purpose of notebook

(-) test and play with advanced numerical EDA methods

todos: (-) …

information

name: makeovermonday_2021w22 link: https://data.world/makeovermonday/2021w22 title: 2021/W22: The Plastic Waste Makers Index Data Source: Minderoo from 2019

insights
  1. correlation - most of the columns are highly correlated, that was to be expected, since most variables are depend on each other, e.g., rigid + flexible = total c production, total -> -rank rigid overall has a less strong correlation with the other variables, which might hint to there being a a different sub-population based on rigid production, flexible has a stronger correlation with total as rigid and total, since flexible is a far bigger contribution to total flexible has a stronger correlation with the overall production, than rigid, this is interesting and also might hint to rigid producers have a different market strategy than flexible producers
  2. a Gini coefficient of 0.56 is quite high, which means that only a top few producers are responsible for a large amount of the total SUP waste contribution, in other words 10% are responsible for 44% of the waste
  3. I need more knowledge how to work with and interpret SOM and PCA, maybe also not enough observations in data set,
    but the clustering from SOM, when only independent variables are used, actually show the two assumed subpopulations based on rigid vs flexible production
  4. parallel coordinate plot is not very insightful, since there is so much data close together, we cannot see any grouping without prior knowledge, only hint is when you zoom in at the bulk of the data in the lower third (y direction), there is a negative correlation
  5. remind that the difference with the partition by k-means is that for hierarchical clustering, the number of classes is not specified in advance
    it seems that most clusters are confused by some specific values (81, 63, 72, 36?), are they some sort of outlier, or what is special about them?
  6. As a reminder, this method aims at partitioning n observations into k clusters in which each observation belongs to the cluster with the closest average, serving as a prototype of the cluster works not quite as well as SOM, but very close, also recommend cluster is three, but closely followed by 2, since 3 does not reveal any significant connection right now, I wonder what that shows
references
  1. https://iamciera.github.io/SOMexample/html/SOM_RNAseq_tutorial_part2a_SOM.html
  2. https://statsandr.com/blog/clustering-analysis-k-means-and-hierarchical-clustering-by-hand-and-in-r/#optimal-number-of-clusters
  3. https://lukedaniels1.github.io/Bio381_2018/Daniels_Cluster_Analysis_Lecture.html
  4. https://towardsdatascience.com/10-tips-for-choosing-the-optimal-number-of-clusters-277e93d72d92
load packages & global options
overview
library(tidyverse) # tidy data frame
library(ggthemes) # for extra plot themes
library(plotly) # make ggplots interactive

library(factoextra) # provides some easy-to-use functions to extract and visualize the output of multivariate data analyses

# individual libraries are in the according cell

knitr::opts_chunk$set(
  # fig.width = 15, fig.height = 9, 
  warning = FALSE
)

# plotly: ,width = 900, height = 550
head(plastic)
correlation
most of the columns are highly correlated, that was to be expected, since most variables are depend on each other, e.g., rigid + flexible = total c production, total -> -rank rigid overall has a less strong correlation with the other variables, which might hint to there being a a different sub-population based on rigid production, flexible has a stronger correlation with total as rigid and total, since flexible is a far bigger contribution to total flexible has a stronger correlation with the overall production, than rigid, this is interesting and also might hint to rigid producers have a different market strategy than flexible producers
%>% select(-rank, -total, -assets) can show a more clear picture by removing dependent variables
r summary(plastic)
polymer_producer rank no_of_assets production_of_in_scope_polymers flexible_format_contribution_to_sup_waste rigid_format_contribution_to_sup_waste Length:100 Min. : 1.00 Min. : 0.00 Min. : 0.200 Min. :0.000 Min. :0.000 Class :character 1st Qu.: 25.75 1st Qu.: 3.00 1st Qu.: 0.500 1st Qu.:0.100 1st Qu.:0.100 Mode :character Median : 50.50 Median : 6.00 Median : 0.900 Median :0.200 Median :0.200 Mean : 50.50 Mean :11.56 Mean : 1.805 Mean :0.538 Mean :0.416 3rd Qu.: 75.25 3rd Qu.:12.25 3rd Qu.: 1.700 3rd Qu.:0.500 3rd Qu.:0.500 Max. :100.00 Max. :82.00 Max. :11.600 Max. :4.700 Max. :4.500 total_contribution_to_sup_waste total_waste_div_production Min. :0.200 Min. :0.3000 1st Qu.:0.300 1st Qu.:0.4300 Median :0.450 Median :0.5000 Mean :0.950 Mean :0.5834 3rd Qu.:0.925 3rd Qu.:0.6900 Max. :5.900 Max. :1.0000

Lorenz curve & Gini coefficient

a Gini coefficient of 0.56 is quite high, which means that only a top few producers are responsible for a large amount of the total SUP waste contribution, in other words 10% are responsible for 44% of the waste

name = c('')
df <- plastic %>% select(-polymer_producer, - total_waste_div_production) %>% mutate(rank = -rank) %>% # change sign of rank to make it increase with the dependent variables
  rename( assets = no_of_assets, 
          product = production_of_in_scope_polymers, 
          flexible = flexible_format_contribution_to_sup_waste, 
          rigid = rigid_format_contribution_to_sup_waste, 
          total = total_contribution_to_sup_waste) 


library(corrplot) # correlation plots
# https://cran.r-project.org/web/packages/corrplot/vignettes/corrplot-intro.html

cor <- cor(df)
cor_mtest <- cor.mtest(df, conf.level = 0.99) # combining correlogram with significance test
corrplot(cor, method = "number", order = 'hclust', addrect = 3, p.mat = cor_mtest$p, insig = "pch") # order = AOE, FPC, hclust + addrect


corrplot(cor, p.mat = cor_mtest$p, low = cor_mtest$lowCI, upp = cor_mtest$uppCI, order = 'hclust', sig.level = 0.01, tl.pos = 'd', addrect = 3, rect.col = 'navy', plotC = 'rect', cl.pos = 'n', insig = "p-value")

name = c('total_contribution_to_sup_waste', 'Producers')
df <- plastic %>% rename(value = total_contribution_to_sup_waste) %>% select(value) 

library(gglorenz) #transformations for plotting Lorenz curve, https://github.com/jjchern/gglorenz

lorenzcurve <- df %>% 
  ggplot(aes(value)) +
    stat_lorenz(desc = FALSE) +
    coord_fixed() +
    geom_abline(linetype = 'dashed') +
    theme_minimal() +
    hrbrthemes::scale_x_percent() +
    hrbrthemes::scale_y_percent() +
    # hrbrthemes::theme_ipsum_rc() +
    annotate_ineq(df$value) +
    ggtitle(paste("Lorenz curve for", name[1], sep=" ")) 
lorenzcurve <- ggplotly(lorenzcurve) %>% layout(yaxis = list(title = paste("cumulative percentage of", name[1], sep=" ")), xaxis = list(title = paste("cumulative percentage of", name[2], sep=" "))) 

lorenzcurve
principal component analysis colored by self organizing map cluster

I need more knowledge how to work with and interpret SOM and PCA, maybe also not enough observations in data set https://iamciera.github.io/SOMexample/html/SOM_RNAseq_tutorial_part2a_SOM.html

but the clustering from SOM, when only independent variables are used, actually show the two assumed subpopulations based on rigid vs flexible produciton

name = c('flexible_format_contribution_to_sup_waste', 'rigid_format_contribution_to_sup_waste', 'Producers', 'flexible', 'rigid')
df <- plastic %>% rename(flexible = flexible_format_contribution_to_sup_waste, rigid = rigid_format_contribution_to_sup_waste) %>% select(flexible, rigid) %>% pivot_longer(cols = c(flexible,rigid))

library(gglorenz) #transformations for plotting Lorenz curve, https://github.com/jjchern/gglorenz

# get ggplot standard colors for grouping, which are equally spaced hues around the color wheel, starting from 15
gg_color_hue <- function(n) {
  hues = seq(15, 375, length = n + 1)
  hcl(h = hues, l = 65, c = 100)[1:n]
}

lorenzcurve <- df %>% 
  ggplot(aes(x = value, color = name)) +
    stat_lorenz(desc = FALSE) +
    coord_fixed() +
    geom_abline(linetype = 'dashed') +
    theme_minimal() +
    hrbrthemes::scale_x_percent() +
    hrbrthemes::scale_y_percent() +
    # hrbrthemes::theme_ipsum_rc() +
    annotate_ineq(filter(df, name == name[4])$value, y = 0.95, colour = gg_color_hue(2)[1]) +
    annotate_ineq(filter(df, name == name[5])$value, y = 0.90, colour = gg_color_hue(2)[2]) +
    ggtitle(paste("compare Lorenz curve of", name[1], "and", name[2], sep=" ")) 
lorenzcurve <- ggplotly(lorenzcurve) %>% layout(yaxis = list(title = paste("cumulative percentage of<br>", name[1], "<br>", name[2], sep="")), xaxis = list(title = paste("cumulative percentage of", name[2], sep=" "))) 

lorenzcurve
name = c('polymer_producer')
df <- plastic %>% select(- total_waste_div_production, -rank, -no_of_assets, -total_contribution_to_sup_waste) %>% # removed variables which are depended on each other
  rename( product = production_of_in_scope_polymers, 
          flexible = flexible_format_contribution_to_sup_waste, 
          rigid = rigid_format_contribution_to_sup_waste)


library(kohonen) # functions to train self-organising maps (SOMs)

# setup for pca
scale_data <- as.matrix(t(scale(t(df[, !names(df) %in% name])))) # We need to normalize the data based on scale function because the variables are different scales; Normalization means subtracting mean from each observation and dividing with standard deviation. Check all the variables mean values are zero now
head(scale_data)
       product   flexible      rigid
[1,] 1.0838622 -0.1970659 -0.8867964
[2,] 1.0301899 -0.0633963 -0.9667936
[3,] 1.1172018 -0.3058262 -0.8113756
[4,] 0.6859477 -1.1474034  0.4614557
[5,] 1.1208971 -0.3202563 -0.8006408
[6,] 1.0995250 -0.2443389 -0.8551861
# principle component analysis
pca <- prcomp(scale_data, scale=TRUE)
summary(pca)
Importance of components:
                          PC1     PC2       PC3
Standard deviation     1.6591 0.49736 4.375e-16
Proportion of Variance 0.9175 0.08246 0.000e+00
Cumulative Proportion  0.9175 1.00000 1.000e+00
# visualize pcs results
# Contributions of variables to PC1
fviz_contrib(pca, choice = "var", axes = 1, top = 10)

# Contributions of variables to PC2
fviz_contrib(pca, choice = "var", axes = 2, top = 10)

# Control variable colors using their contributions to the principle axis
fviz_pca_var(pca, col.var="contrib",
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
             repel = TRUE # Avoid text overlapping
             ) + theme_minimal() + ggtitle("Variables - PCA")


# add back to original so everything is together
pca_scores <- data.frame(pca$x)
data_val <- cbind(df, pca_scores)

pca_plot <- ggplot(data_val, aes(x = PC1, y = PC2)) +
    geom_rug(alpha = 0.5) + # two 1d marginal distributions, display individual cases so are best used with smaller datasets
    geom_density_2d(alpha = 0.2, bins = 4) +# 2D kernel density estimation using MASS::kde2d() and display the results with contours
    geom_point(alpha = 0.75) + # point geom is used to create scatterplots
    theme_minimal()
pca_plot <- ggplotly(pca_plot) %>% layout()

pca_plot

# clustering is performed using the som() function on the scaled gene expression values.
set.seed(3)

# define a grid for the SOM and train
grid_size <- ncol(scale_data)
som_grid <- somgrid(xdim = grid_size, ydim = grid_size, topo = 'hexagonal')
som_model <- som(scale_data, grid = som_grid)
summary(som_model)
SOM of size 3x3 with a hexagonal topology and a bubble neighbourhood function.
The number of data layers is 1.
Distance measure(s) used: sumofsquares.
Training data included: 100 objects.
Mean distance to the closest unit in the map: 0.007.
# generate som plots after training
plot(som_model, type = 'mapping')

plot(som_model, type = 'codes')

# plot(som_model, type = 'counts')
# plot(som_model, type = 'dist.neighbours')
# plot(som_model, type = 'quality')
# plot(som_model, type = 'changes')

# further split the clusters into a smaller set of clusters using hierarchical clustering.
som_cluster <- cutree(hclust(dist(som_model$codes[[1]])), 2) # use hierarchical clustering to cluster the codebook vectors

plot(som_model, type="mapping", bgcol = som_cluster, main = "Clusters")
add.cluster.boundaries(som_model, som_cluster)


# attach the hierchal cluster to the larger dataset data_val.
gridSquare <- grid_size * grid_size
som_clusterKey <- data.frame(som_cluster)
som_clusterKey$unit_classif <- c(1:gridSquare)
data_val <- cbind(data_val,som_model$unit.classif,som_model$distances) %>% rename(unit_classif = 'som_model$unit.classif', distances = 'som_model$distances')
data_val <- merge(data_val, som_clusterKey, by.x = "unit_classif" )
head(data_val)

# plot pca with colored clusters
pcasom_plot <- ggplot(data_val, aes(x = PC1, y = PC2, color = factor(som_cluster), text = polymer_producer)) +
    geom_rug(alpha = 0.5) + # two 1d marginal distributions, display individual cases so are best used with smaller datasets
    geom_point(alpha = 0.75) + # point geom is used to create scatterplots
    theme_minimal()
pcasom_plot <- ggplotly(pcasom_plot) %>% layout()

pcasom_plot
parallel coordinate plot

parallel coordinate plot is not very insightful, since there is so much data close together, we cannot see any grouping without prior knowledge, only hint is when you zoom in at the bulk of the data in the lower third (y direction), there is a negative correlation

# two variables, continuous x, continuous y, show trend and distribution
name = c('production_of_in_scope_polymers', 'total_contribution_to_sup_waste')
df <- merge(plastic, data_val, by.x = 'polymer_producer')
df <- df %>% rename(x = production_of_in_scope_polymers, y = total_contribution_to_sup_waste, cluster = som_cluster, text = polymer_producer) %>% select(x, y, cluster, text) 

# https://ggplot2.tidyverse.org/reference/geom_smooth.html
point_plot <- df %>%
  ggplot(aes(x = x, y = y, color = factor(cluster))) +
    # geom_jitter(alpha = 0.5, size = 1) +
    geom_rug(alpha = 0.5) + # two 1d marginal distributions, display individual cases so are best used with smaller datasets
    geom_density_2d(alpha = 0.2, bins = 4) +# 2D kernel density estimation using MASS::kde2d() and display the results with contours
    geom_smooth(fill = "grey90") + # aids the eye in seeing patterns in the presence of overplotting
    geom_point(aes(text = text), alpha = 0.75) + # point geom is used to create scatterplots
    theme_minimal() +
    ggtitle(paste("trend of", name[2], "over", name[1], sep=" ")) 
point_plot <- ggplotly(point_plot) %>% layout(xaxis = list(showticklabels = FALSE))
`geom_smooth()` using method = 'loess' and formula 'y ~ x'
x_density_plot <- df %>%
  ggplot(aes(x = x, color = factor(cluster))) +
    stat_density(geom="line") + # draws kernel density estimate, which is a smoothed version of the histogram
    # geom_histogram(binwidth = 1) +
    theme_minimal() 
x_density_plot <- ggplotly(x_density_plot) %>% layout(yaxis = list(showticklabels = FALSE, showgrid = FALSE), xaxis = list(showticklabels = FALSE, showgrid = FALSE))

y_density_plot <- df %>%
  ggplot(aes(x = y, color = factor(cluster))) +
    stat_density(geom="line") + # draws kernel density estimate, which is a smoothed version of the histogram
    # geom_histogram(binwidth = 1) +
    coord_flip() +
    theme_minimal() 
y_density_plot <- ggplotly(y_density_plot) %>% layout(yaxis = list(showticklabels = FALSE, showgrid = FALSE), xaxis = list(showticklabels = FALSE, showgrid = FALSE))

# https://ggplot2.tidyverse.org/reference/geom_quantile.html
qualtile_plot <- df %>%
  ggplot(aes(x = x, y = y, color = factor(cluster))) +
    geom_quantile(alpha = 0.8) + # fits a quantile regression to the data and draws the fitted quantiles with lines
    theme_minimal() 
qualtile_plot <- ggplotly(qualtile_plot) %>% layout(yaxis = list(showticklabels = FALSE, showgrid = FALSE))
Smoothing formula not specified. Using: y ~ x
Smoothing formula not specified. Using: y ~ x
# merge figures into one plot, via subplots, https://plotly-r.com/arranging-views.html
sub1 <- subplot(x_density_plot, plotly_empty(), point_plot, y_density_plot, nrows = 2, margin = 0, heights = c(0.15, 0.85), widths = c(0.9, 0.1), shareX = TRUE, shareY = TRUE, titleX = FALSE, titleY = FALSE) %>% layout()
No trace type specified:
  Based on info supplied, a 'scatter' trace seems appropriate.
  Read more about this trace type -> https://plotly.com/r/reference/#scatter
No scatter mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode
sub2 <- subplot(qualtile_plot, plotly_empty(), margin = 0, widths = c(0.9, 0.10), titleX = FALSE, titleY = FALSE) %>% layout()
No trace type specified:
  Based on info supplied, a 'scatter' trace seems appropriate.
  Read more about this trace type -> https://plotly.com/r/reference/#scatter
No scatter mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode
fig <- subplot(sub1, sub2, nrows = 2, margin = 0, heights = c(0.8, 0.2), shareX = TRUE) %>% layout(xaxis = list(title = name[1]), yaxis = list(title = name[2]))
  
fig
k-means clustering

https://statsandr.com/blog/clustering-analysis-k-means-and-hierarchical-clustering-by-hand-and-in-r/#optimal-number-of-clusters As a reminder, this method aims at partitioning n observations into k clusters in which each observation belongs to the cluster with the closest average, serving as a prototype of the cluster works not quite as well as SOM, but very close, also recommend cluster is three, but closely followed by 2, since 3 does not reveal any significant connection right now, I wonder what that shows

name = c('plastic producers clustered by focus')
df <- merge(plastic, select(data_val, som_cluster, polymer_producer), by.x = 'polymer_producer') %>%
  mutate(som_cluster = as.character(som_cluster)) %>%
  rename(product = production_of_in_scope_polymers, 
          flexible = flexible_format_contribution_to_sup_waste, 
          rigid = rigid_format_contribution_to_sup_waste,
          assets =  no_of_assets,
          cluster = som_cluster) %>% 
  select(assets, product, flexible, rigid, cluster) 

library(GGally) # extends ggplot2 by adding several functions to reduce the complexity of combining geoms with transformed data

# https://www.r-graph-gallery.com/parallel-plot-ggally.html#custom
parcoord_plot <- ggparcoord(df,
           columns = 1:4, groupColumn = 5,
           scale='center', # scaling: standardize and center variables
           showPoints = TRUE,
           title = name,
           alphaLines = 0.3) +
  theme_minimal() 
parcoord_plot <- ggplotly(parcoord_plot) %>% layout(autosize=T)

parcoord_plot
name = c('polymer_producer')
df <- plastic %>% select(- total_waste_div_production, -rank, -no_of_assets, -total_contribution_to_sup_waste) %>% # removed variables which are depended on each other
  rename( product = production_of_in_scope_polymers, 
          flexible = flexible_format_contribution_to_sup_waste, 
          rigid = rigid_format_contribution_to_sup_waste)

library(NbClust) # determining the optimal number of clusters in a data set
library(cluster) # computes agglomerative hierarchical clustering of the dataset

scale_data <- as.matrix(t(scale(t(df[, !names(df) %in% name])))) # We need to normalize the data based on scale function because the variables are different scales; Normalization means subtracting mean from each observation and dividing with standard deviation. Check all the variables mean values are zero now
head(scale_data)
       product   flexible      rigid
[1,] 1.0838622 -0.1970659 -0.8867964
[2,] 1.0301899 -0.0633963 -0.9667936
[3,] 1.1172018 -0.3058262 -0.8113756
[4,] 0.6859477 -1.1474034  0.4614557
[5,] 1.1208971 -0.3202563 -0.8006408
[6,] 1.0995250 -0.2443389 -0.8551861
kmeans_model <- kmeans(scale_data, centers = 2, nstart = 12) #  k-means clustering is done via the kmeans() function, with the argument centers that corresponds to the number of desired clusters
df_cluster <- tibble(df, cluster = as.factor(kmeans_model$cluster)) # store cluster in original data set as column
head(df_cluster)

# check quality of a k-means partition
quality <- kmeans_model$betweenss / kmeans_model$totss 
print(paste("quality of kmeans is BSS/TSS: ", format(round(quality,2), nsmall = 2)))
[1] "quality of kmeans is BSS/TSS:  0.88"
# find optimal number of clusters
fviz_nbclust(scale_data, kmeans, method = 'wss') + # Elbow method, needs scaled data
  geom_vline(xintercept = 2, linetype = 2) + # add line for better visualization
  labs(subtitle = "Elbow method") # add subtitle


fviz_nbclust(scale_data, kmeans, method = 'silhouette') + # Silhouette method, needs scaled data
  labs(subtitle = "Silhouette method") # add subtitle


fviz_nbclust(df[, !names(df) %in% name], kmeans, # Gap statistics, needs original data ?
             nstart = 25,
             method = 'gap_stat',
             nboot = 100) + # reduce it for lower computation time, but less precise results
  labs(subtitle = "Gap statistics method")
Clustering k = 1,2,..., K.max (= 10): .. done
Bootstrapping, b = 1,2,..., B (= 100)  [one "." per sample]:
.................................................. 50 
.................................................. 100 

nbclust_out <- NbClust(data = df[, !names(df) %in% name], # NbClust needs the original data ?
                       distance = 'euclidean',
                       min.nc = 2, # minimum number of clusters
                       max.nc = 10, # maximum number of cluster
                       method = 'complete',
                       index = 'all')
[1] "Frey index : No clustering structure in this data set"
*** : The Hubert index is a graphical method of determining the number of clusters.
                In the plot of Hubert index, we seek a significant knee that corresponds to a 
                significant increase of the value of the measure i.e the significant peak in Hubert
                index second differences plot. 
 

*** : The D index is a graphical method of determining the number of clusters. 
                In the plot of D index, we seek a significant knee (the significant peak in Dindex
                second differences plot) that corresponds to a significant increase of the value of
                the measure. 
 
******************************************************************* 
* Among all indices:                                                
* 7 proposed 2 as the best number of clusters 
* 9 proposed 3 as the best number of clusters 
* 1 proposed 5 as the best number of clusters 
* 2 proposed 6 as the best number of clusters 
* 2 proposed 9 as the best number of clusters 
* 2 proposed 10 as the best number of clusters 

                   ***** Conclusion *****                            
 
* According to the majority rule, the best number of clusters is  3 
 
 
******************************************************************* 

fviz_nbclust(nbclust_out) + theme_minimal() +
  labs(subtitle = "NbClust results")
Among all indices: 
===================
* 2 proposed  0 as the best number of clusters
* 7 proposed  2 as the best number of clusters
* 9 proposed  3 as the best number of clusters
* 1 proposed  5 as the best number of clusters
* 2 proposed  6 as the best number of clusters
* 2 proposed  9 as the best number of clusters
* 2 proposed  10 as the best number of clusters
* 1 proposed  NA's as the best number of clusters

Conclusion
=========================
* According to the majority rule, the best number of clusters is  3 .

# check quality of clustering
# if a large majority of the silhouette coefficients are positive, it indicates that the observations are placed in the correct group
sil <- silhouette(kmeans_model$cluster, dist(scale_data)) 
fviz_silhouette(sil)


fviz_cluster(kmeans_model, df[, !names(df) %in% name], ellipse.type = 'norm') + theme_minimal()

name = c('polymer_producer')
df <- plastic %>% select(- total_waste_div_production, -rank, -no_of_assets, -total_contribution_to_sup_waste) %>% # removed variables which are depended on each other
  rename( product = production_of_in_scope_polymers, 
          flexible = flexible_format_contribution_to_sup_waste, 
          rigid = rigid_format_contribution_to_sup_waste)

scale_data <- as.matrix(t(scale(t(df[, !names(df) %in% name])))) # We need to normalize the data based on scale function because the variables are different scales; Normalization means subtracting mean from each observation and dividing with standard deviation. Check all the variables mean values are zero now

kmean_calc <- function(df, ...){
  kmeans(df, scaled = ..., nstart = 30)
}

km2 <- kmean_calc(scale_data, 2)
km3 <- kmean_calc(scale_data, 3)
km4 <- kmeans(scale_data, 4)
km5 <- kmeans(scale_data, 5)
km6 <- kmeans(scale_data, 6)
km7 <- kmeans(scale_data, 7)

p1 <- fviz_cluster(km2, data = scale_data, ellipse.type = "convex") + theme_minimal() + ggtitle("k = 2") 
p2 <- fviz_cluster(km3, data = scale_data, ellipse.type = "convex") + theme_minimal() + ggtitle("k = 3")
p3 <- fviz_cluster(km4, data = scale_data, ellipse.type = "convex") + theme_minimal() + ggtitle("k = 4")
p4 <- fviz_cluster(km5, data = scale_data, ellipse.type = "convex") + theme_minimal() + ggtitle("k = 5")
p5 <- fviz_cluster(km6, data = scale_data, ellipse.type = "convex") + theme_minimal() + ggtitle("k = 6")
p6 <- fviz_cluster(km7, data = scale_data, ellipse.type = "convex") + theme_minimal() + ggtitle("k = 7")

fig <- subplot(p1, p2, p3 , p4, p5, p6, nrows = 2, shareX = TRUE, shareY = TRUE) %>% layout(autosize=T) # TOOD: make all plots linked
fig
hierarchical clustering

https://statsandr.com/blog/clustering-analysis-k-means-and-hierarchical-clustering-by-hand-and-in-r/#optimal-number-of-clusters remind that the difference with the partition by k-means is that for hierarchical clustering, the number of classes is not specified in advance

it seems that most clusters are confused by some specific values (81, 63, 72, 36?), are they some sort of outlier, or what is special about them?

# two variables, continuous x, continuous y, show trend and distribution
name = c('production_of_in_scope_polymers', 'total_contribution_to_sup_waste')
df <- tibble(plastic, cluster = as.factor(kmeans_model$cluster))
df <- df %>% rename(x = production_of_in_scope_polymers, y = total_contribution_to_sup_waste, cluster = cluster, text = polymer_producer) %>% select(x, y, cluster, text) 

# https://ggplot2.tidyverse.org/reference/geom_smooth.html
point_plot <- df %>%
  ggplot(aes(x = x, y = y, color = factor(cluster))) +
    # geom_jitter(alpha = 0.5, size = 1) +
    geom_rug(alpha = 0.5) + # two 1d marginal distributions, display individual cases so are best used with smaller datasets
    geom_density_2d(alpha = 0.2, bins = 4) +# 2D kernel density estimation using MASS::kde2d() and display the results with contours
    geom_smooth(fill = "grey90") + # aids the eye in seeing patterns in the presence of overplotting
    geom_point(aes(text = text), alpha = 0.75) + # point geom is used to create scatterplots
    theme_minimal() +
    ggtitle(paste("trend of", name[2], "over", name[1], sep=" ")) 
point_plot <- ggplotly(point_plot) %>% layout(xaxis = list(showticklabels = FALSE))
`geom_smooth()` using method = 'loess' and formula 'y ~ x'
x_density_plot <- df %>%
  ggplot(aes(x = x, color = factor(cluster))) +
    stat_density(geom="line") + # draws kernel density estimate, which is a smoothed version of the histogram
    # geom_histogram(binwidth = 1) +
    theme_minimal() 
x_density_plot <- ggplotly(x_density_plot) %>% layout(yaxis = list(showticklabels = FALSE, showgrid = FALSE), xaxis = list(showticklabels = FALSE, showgrid = FALSE))

y_density_plot <- df %>%
  ggplot(aes(x = y, color = factor(cluster))) +
    stat_density(geom="line") + # draws kernel density estimate, which is a smoothed version of the histogram
    # geom_histogram(binwidth = 1) +
    coord_flip() +
    theme_minimal() 
y_density_plot <- ggplotly(y_density_plot) %>% layout(yaxis = list(showticklabels = FALSE, showgrid = FALSE), xaxis = list(showticklabels = FALSE, showgrid = FALSE))

# https://ggplot2.tidyverse.org/reference/geom_quantile.html
qualtile_plot <- df %>%
  ggplot(aes(x = x, y = y, color = factor(cluster))) +
    geom_quantile(alpha = 0.8) + # fits a quantile regression to the data and draws the fitted quantiles with lines
    theme_minimal() 
qualtile_plot <- ggplotly(qualtile_plot) %>% layout(yaxis = list(showticklabels = FALSE, showgrid = FALSE))
Smoothing formula not specified. Using: y ~ x
Smoothing formula not specified. Using: y ~ x
# merge figures into one plot, via subplots, https://plotly-r.com/arranging-views.html
sub1 <- subplot(x_density_plot, plotly_empty(), point_plot, y_density_plot, nrows = 2, margin = 0, heights = c(0.15, 0.85), widths = c(0.9, 0.1), shareX = TRUE, shareY = TRUE, titleX = FALSE, titleY = FALSE) %>% layout()
No trace type specified:
  Based on info supplied, a 'scatter' trace seems appropriate.
  Read more about this trace type -> https://plotly.com/r/reference/#scatter
No scatter mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode
sub2 <- subplot(qualtile_plot, plotly_empty(), margin = 0, widths = c(0.9, 0.10), titleX = FALSE, titleY = FALSE) %>% layout()
No trace type specified:
  Based on info supplied, a 'scatter' trace seems appropriate.
  Read more about this trace type -> https://plotly.com/r/reference/#scatter
No scatter mode specifed:
  Setting the mode to markers
  Read more about this attribute -> https://plotly.com/r/reference/#scatter-mode
fig <- subplot(sub1, sub2, nrows = 2, margin = 0, heights = c(0.8, 0.2), shareX = TRUE) %>% layout(xaxis = list(title = name[1]), yaxis = list(title = name[2]))

fig
Clustree
https://towardsdatascience.com/10-tips-for-choosing-the-optimal-number-of-clusters-277e93d72d92 In this figure the size of each node corresponds to the number of samples in each cluster, and the arrows are coloured according to the number of samples each cluster receives. A separate set of arrows, the transparent ones, called the incoming node proportion, are also coloured and shows how samples from one group end up in another group — an indicator of cluster instability.
```r name = c(‘polymer_producer’) df <- plastic %>% select(- total_waste_div_production, -rank, -no_of_assets, -total_contribution_to_sup_waste) %>% # removed variables which are depended on each other rename( product = production_of_in_scope_polymers, flexible = flexible_format_contribution_to_sup_waste, rigid = rigid_format_contribution_to_sup_waste)
scale_data <- as.matrix(t(scale(t(df[, !names(df) %in% name])))) # We need to normalize the data based on scale function because the variables are different scales; Normalization means subtracting mean from each observation and dividing with standard deviation. Check all the variables mean values are zero now head(scale_data) ```
product flexible rigid [1,] 1.0838622 -0.1970659 -0.8867964 [2,] 1.0301899 -0.0633963 -0.9667936 [3,] 1.1172018 -0.3058262 -0.8113756 [4,] 0.6859477 -1.1474034 0.4614557 [5,] 1.1208971 -0.3202563 -0.8006408 [6,] 1.0995250 -0.2443389 -0.8551861
```r no_k = 2;
# Hierarchical clustering: single linkage hclust_res <- hclust(dist(scale_data), method = ‘single’) fviz_dend(hclust_res, k = no_k, rect = TRUE) ```
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.row names were found from a short variable and have been discarded
r # Hierarchical clustering: complete linkage hclust_res <- hclust(dist(scale_data), method = 'complete') plot(hclust_res) rect.hclust(hclust_res, k = no_k, border = 'blue')
```r
# Hierarchical clustering: average linkage hclust_res <- hclust(dist(scale_data), method = ‘average’) plot(hclust_res) rect.hclust(hclust_res, k = no_k, border = ‘blue’) ```
```r
# Hierarchical clustering: ward hclust_res <- hclust(dist(scale_data), method = ‘ward.D’) fviz_dend(hclust_res, k = no_k, rect = TRUE) ```
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.row names were found from a short variable and have been discarded
r # Hierarchical clustering: mcquitty hclust_res <- hclust(dist(scale_data), method = 'mcquitty') plot(hclust_res) rect.hclust(hclust_res, k = no_k, border = 'blue')
```r
# Hierarchical clustering: centroid hclust_res <- hclust(dist(scale_data), method = ‘centroid’) plot(hclust_res) rect.hclust(hclust_res, k = no_k, border = ‘blue’) ```

cValid to choose best clustering algo

https://towardsdatascience.com/10-tips-for-choosing-the-optimal-number-of-clusters-277e93d72d92 The cValid package can be used to simultaneously compare multiple clustering algorithms, to identify the best clustering approach and the optimal number of clusters. We will compare k-means, hierarchical and PAM clustering.

LS0tDQp0aXRsZTogImRpc2NvdmVyIGFuZCB0cmFuc2Zvcm0gcGxhc3RpYyB3YXN0ZSBtYWtlcnMgaW5kZXggZGF0YSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCi0tLQ0KcHVycG9zZSBvZiBub3RlYm9vaw0KLS0tDQoNCiAgKC0pIHRlc3QgYW5kIHBsYXkgd2l0aCBhZHZhbmNlZCBudW1lcmljYWwgRURBIG1ldGhvZHMNCiAgDQp0b2RvczoNCiAgKC0pIC4uLg0KICANCi0tLQ0KaW5mb3JtYXRpb24NCi0tLQ0KDQpuYW1lOiBtYWtlb3Zlcm1vbmRheV8yMDIxdzIyDQpsaW5rOiBodHRwczovL2RhdGEud29ybGQvbWFrZW92ZXJtb25kYXkvMjAyMXcyMg0KdGl0bGU6IDIwMjEvVzIyOiBUaGUgUGxhc3RpYyBXYXN0ZSBNYWtlcnMgSW5kZXgNCkRhdGEgU291cmNlOiBbTWluZGVyb29dKGh0dHBzOi8vd3d3Lm1pbmRlcm9vLm9yZy9wbGFzdGljLXdhc3RlLW1ha2Vycy1pbmRleC9kYXRhL2luZGljZXMvcHJvZHVjZXJzLykgZnJvbSAyMDE5DQogIA0KLS0tDQppbnNpZ2h0cyANCi0tLQ0KDQogIChpKSBjb3JyZWxhdGlvbiAtIG1vc3Qgb2YgdGhlIGNvbHVtbnMgYXJlIGhpZ2hseSBjb3JyZWxhdGVkLCB0aGF0IHdhcyB0byBiZSBleHBlY3RlZCwgc2luY2UgbW9zdCB2YXJpYWJsZXMgYXJlIGRlcGVuZCBvbiBlYWNoIG90aGVyLCBlLmcuLCByaWdpZCArIGZsZXhpYmxlID0gdG90YWwgYyBwcm9kdWN0aW9uLCAgICAgICAgICAgICAgICAgICAgICAgICB0b3RhbCAtPiAtcmFuaw0KICAgICAgICAgICAgICAgICAgICByaWdpZCBvdmVyYWxsIGhhcyBhIGxlc3Mgc3Ryb25nIGNvcnJlbGF0aW9uIHdpdGggdGhlIG90aGVyIHZhcmlhYmxlcywgd2hpY2ggbWlnaHQgaGludCB0byB0aGVyZSBiZWluZyBhIGEgZGlmZmVyZW50IHN1Yi1wb3B1bGF0aW9uIGJhc2VkIG9uIHJpZ2lkIHByb2R1Y3Rpb24sIA0KICAgICAgICAgICAgICAgICAgICBmbGV4aWJsZSBoYXMgYSBzdHJvbmdlciBjb3JyZWxhdGlvbiB3aXRoIHRvdGFsIGFzIHJpZ2lkIGFuZCB0b3RhbCwgc2luY2UgZmxleGlibGUgaXMgYSBmYXIgYmlnZ2VyIGNvbnRyaWJ1dGlvbiB0byB0b3RhbA0KICAgICAgICAgICAgICAgICAgICBmbGV4aWJsZSBoYXMgYSBzdHJvbmdlciBjb3JyZWxhdGlvbiB3aXRoIHRoZSBvdmVyYWxsIHByb2R1Y3Rpb24sIHRoYW4gcmlnaWQsIHRoaXMgaXMgaW50ZXJlc3RpbmcgYW5kIGFsc28gbWlnaHQgaGludCB0byByaWdpZCBwcm9kdWNlcnMgaGF2ZSBhIGRpZmZlcmVudCBtYXJrZXQgICAgICAgICAgICAgICAgICAgICAgICBzdHJhdGVneSB0aGFuIGZsZXhpYmxlIHByb2R1Y2Vycw0KICAoaSkgYSBHaW5pIGNvZWZmaWNpZW50IG9mIDAuNTYgaXMgcXVpdGUgaGlnaCwgd2hpY2ggbWVhbnMgdGhhdCBvbmx5IGEgdG9wIGZldyBwcm9kdWNlcnMgYXJlIHJlc3BvbnNpYmxlIGZvciBhIGxhcmdlIGFtb3VudCBvZiB0aGUgdG90YWwgU1VQIHdhc3RlIGNvbnRyaWJ1dGlvbiwgaW4gb3RoZXIgd29yZHMgMTAlICAgICAgICAgYXJlIHJlc3BvbnNpYmxlIGZvciA0NCUgb2YgdGhlIHdhc3RlDQogIChpKSBJIG5lZWQgbW9yZSBrbm93bGVkZ2UgaG93IHRvIHdvcmsgd2l0aCBhbmQgaW50ZXJwcmV0IFNPTSBhbmQgUENBLCBtYXliZSBhbHNvIG5vdCBlbm91Z2ggb2JzZXJ2YXRpb25zIGluIGRhdGEgc2V0LCAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgYnV0IHRoZSBjbHVzdGVyaW5nIGZyb20gU09NLCB3aGVuIG9ubHkgaW5kZXBlbmRlbnQgdmFyaWFibGVzIGFyZSB1c2VkLCBhY3R1YWxseSBzaG93IHRoZSB0d28gYXNzdW1lZCBzdWJwb3B1bGF0aW9ucyBiYXNlZCBvbiByaWdpZCB2cyBmbGV4aWJsZSBwcm9kdWN0aW9uDQogIChpKSBwYXJhbGxlbCBjb29yZGluYXRlIHBsb3QgaXMgbm90IHZlcnkgaW5zaWdodGZ1bCwgc2luY2UgdGhlcmUgaXMgc28gbXVjaCBkYXRhIGNsb3NlIHRvZ2V0aGVyLCB3ZSBjYW5ub3Qgc2VlIGFueSBncm91cGluZyB3aXRob3V0IHByaW9yIGtub3dsZWRnZSwgb25seSBoaW50IGlzIHdoZW4geW91IHpvb20gaW4gYXQgICAgICAgdGhlIGJ1bGsgb2YgdGhlIGRhdGEgaW4gdGhlIGxvd2VyIHRoaXJkICh5IGRpcmVjdGlvbiksIHRoZXJlIGlzIGEgbmVnYXRpdmUgY29ycmVsYXRpb24NCiAgKGkpIHJlbWluZCB0aGF0IHRoZSBkaWZmZXJlbmNlIHdpdGggdGhlIHBhcnRpdGlvbiBieSBrLW1lYW5zIGlzIHRoYXQgZm9yIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nLCB0aGUgbnVtYmVyIG9mIGNsYXNzZXMgaXMgbm90IHNwZWNpZmllZCBpbiBhZHZhbmNlICAgDQogICAgICBpdCBzZWVtcyB0aGF0IG1vc3QgY2x1c3RlcnMgYXJlIGNvbmZ1c2VkIGJ5IHNvbWUgc3BlY2lmaWMgdmFsdWVzICg4MSwgNjMsIDcyLCAzNj8pLCBhcmUgdGhleSBzb21lIHNvcnQgb2Ygb3V0bGllciwgb3Igd2hhdCBpcyBzcGVjaWFsIGFib3V0IHRoZW0/DQogIChpKSBBcyBhIHJlbWluZGVyLCB0aGlzIG1ldGhvZCBhaW1zIGF0IHBhcnRpdGlvbmluZyBuIG9ic2VydmF0aW9ucyBpbnRvIGsgY2x1c3RlcnMgaW4gd2hpY2ggZWFjaCBvYnNlcnZhdGlvbiBiZWxvbmdzIHRvIHRoZSBjbHVzdGVyIHdpdGggdGhlIGNsb3Nlc3QgYXZlcmFnZSwgc2VydmluZyBhcyBhIHByb3RvdHlwZSAgICAgICBvZiB0aGUgY2x1c3Rlcg0KICAgICAgd29ya3Mgbm90IHF1aXRlIGFzIHdlbGwgYXMgU09NLCBidXQgdmVyeSBjbG9zZSwgYWxzbyByZWNvbW1lbmQgY2x1c3RlciBpcyB0aHJlZSwgYnV0IGNsb3NlbHkgZm9sbG93ZWQgYnkgMiwgc2luY2UgMyBkb2VzIG5vdCByZXZlYWwgYW55IHNpZ25pZmljYW50IGNvbm5lY3Rpb24gcmlnaHQgbm93LCBJICAgICAgICAgICAgd29uZGVyIHdoYXQgdGhhdCBzaG93cw0KICAgICAgDQotLS0NCnJlZmVyZW5jZXMNCi0tLQ0KICANCiAgKGkpIGh0dHBzOi8vaWFtY2llcmEuZ2l0aHViLmlvL1NPTWV4YW1wbGUvaHRtbC9TT01fUk5Bc2VxX3R1dG9yaWFsX3BhcnQyYV9TT00uaHRtbA0KICAoaSkgaHR0cHM6Ly9zdGF0c2FuZHIuY29tL2Jsb2cvY2x1c3RlcmluZy1hbmFseXNpcy1rLW1lYW5zLWFuZC1oaWVyYXJjaGljYWwtY2x1c3RlcmluZy1ieS1oYW5kLWFuZC1pbi1yLyNvcHRpbWFsLW51bWJlci1vZi1jbHVzdGVycw0KICAoaSkgaHR0cHM6Ly9sdWtlZGFuaWVsczEuZ2l0aHViLmlvL0JpbzM4MV8yMDE4L0RhbmllbHNfQ2x1c3Rlcl9BbmFseXNpc19MZWN0dXJlLmh0bWwNCiAgKGkpIGh0dHBzOi8vdG93YXJkc2RhdGFzY2llbmNlLmNvbS8xMC10aXBzLWZvci1jaG9vc2luZy10aGUtb3B0aW1hbC1udW1iZXItb2YtY2x1c3RlcnMtMjc3ZTkzZDcyZDkyDQogICAgICANCi0tLQ0KbG9hZCBwYWNrYWdlcyAmIGdsb2JhbCBvcHRpb25zDQotLS0NCmBgYHtyLCBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKSAjIHRpZHkgZGF0YSBmcmFtZQ0KbGlicmFyeShnZ3RoZW1lcykgIyBmb3IgZXh0cmEgcGxvdCB0aGVtZXMNCmxpYnJhcnkocGxvdGx5KSAjIG1ha2UgZ2dwbG90cyBpbnRlcmFjdGl2ZQ0KDQpsaWJyYXJ5KGZhY3RvZXh0cmEpICMgcHJvdmlkZXMgc29tZSBlYXN5LXRvLXVzZSBmdW5jdGlvbnMgdG8gZXh0cmFjdCBhbmQgdmlzdWFsaXplIHRoZSBvdXRwdXQgb2YgbXVsdGl2YXJpYXRlIGRhdGEgYW5hbHlzZXMNCg0KIyBpbmRpdmlkdWFsIGxpYnJhcmllcyBhcmUgaW4gdGhlIGFjY29yZGluZyBjZWxsDQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldCgNCiAgIyBmaWcud2lkdGggPSAxNSwgZmlnLmhlaWdodCA9IDksIA0KICB3YXJuaW5nID0gRkFMU0UNCikNCg0KIyBwbG90bHk6ICx3aWR0aCA9IDkwMCwgaGVpZ2h0ID0gNTUwDQpgYGANCg0KLS0tDQpvdmVydmlldw0KLS0tDQpgYGB7cn0NCmhlYWQocGxhc3RpYykNCmBgYA0KYGBge3J9DQpzdW1tYXJ5KHBsYXN0aWMpDQpgYGANCg0KLS0tDQpjb3JyZWxhdGlvbiANCi0tLQ0KbW9zdCBvZiB0aGUgY29sdW1ucyBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQsIHRoYXQgd2FzIHRvIGJlIGV4cGVjdGVkLCBzaW5jZSBtb3N0IHZhcmlhYmxlcyBhcmUgZGVwZW5kIG9uIGVhY2ggb3RoZXIsIGUuZy4sIHJpZ2lkICsgZmxleGlibGUgPSB0b3RhbCBjIHByb2R1Y3Rpb24sIHRvdGFsIC0+IC1yYW5rDQpyaWdpZCBvdmVyYWxsIGhhcyBhIGxlc3Mgc3Ryb25nIGNvcnJlbGF0aW9uIHdpdGggdGhlIG90aGVyIHZhcmlhYmxlcywgd2hpY2ggbWlnaHQgaGludCB0byB0aGVyZSBiZWluZyBhIGEgZGlmZmVyZW50IHN1Yi1wb3B1bGF0aW9uIGJhc2VkIG9uIHJpZ2lkIHByb2R1Y3Rpb24sIA0KZmxleGlibGUgaGFzIGEgc3Ryb25nZXIgY29ycmVsYXRpb24gd2l0aCB0b3RhbCBhcyByaWdpZCBhbmQgdG90YWwsIHNpbmNlIGZsZXhpYmxlIGlzIGEgZmFyIGJpZ2dlciBjb250cmlidXRpb24gdG8gdG90YWwNCmZsZXhpYmxlIGhhcyBhIHN0cm9uZ2VyIGNvcnJlbGF0aW9uIHdpdGggdGhlIG92ZXJhbGwgcHJvZHVjdGlvbiwgdGhhbiByaWdpZCwgdGhpcyBpcyBpbnRlcmVzdGluZyBhbmQgYWxzbyBtaWdodCBoaW50IHRvIHJpZ2lkIHByb2R1Y2VycyBoYXZlIGEgZGlmZmVyZW50IG1hcmtldCBzdHJhdGVneSB0aGFuIGZsZXhpYmxlIHByb2R1Y2Vycw0KDQolPiUgc2VsZWN0KC1yYW5rLCAtdG90YWwsIC1hc3NldHMpIGNhbiBzaG93IGEgbW9yZSBjbGVhciBwaWN0dXJlIGJ5IHJlbW92aW5nIGRlcGVuZGVudCB2YXJpYWJsZXMNCg0KYGBge3J9DQpuYW1lID0gYygnJykNCmRmIDwtIHBsYXN0aWMgJT4lIHNlbGVjdCgtcG9seW1lcl9wcm9kdWNlciwgLSB0b3RhbF93YXN0ZV9kaXZfcHJvZHVjdGlvbikgJT4lIG11dGF0ZShyYW5rID0gLXJhbmspICU+JSAjIGNoYW5nZSBzaWduIG9mIHJhbmsgdG8gbWFrZSBpdCBpbmNyZWFzZSB3aXRoIHRoZSBkZXBlbmRlbnQgdmFyaWFibGVzDQogIHJlbmFtZSggYXNzZXRzID0gbm9fb2ZfYXNzZXRzLCANCiAgICAgICAgICBwcm9kdWN0ID0gcHJvZHVjdGlvbl9vZl9pbl9zY29wZV9wb2x5bWVycywgDQogICAgICAgICAgZmxleGlibGUgPSBmbGV4aWJsZV9mb3JtYXRfY29udHJpYnV0aW9uX3RvX3N1cF93YXN0ZSwgDQogICAgICAgICAgcmlnaWQgPSByaWdpZF9mb3JtYXRfY29udHJpYnV0aW9uX3RvX3N1cF93YXN0ZSwgDQogICAgICAgICAgdG90YWwgPSB0b3RhbF9jb250cmlidXRpb25fdG9fc3VwX3dhc3RlKSANCg0KDQpsaWJyYXJ5KGNvcnJwbG90KSAjIGNvcnJlbGF0aW9uIHBsb3RzDQojIGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9jb3JycGxvdC92aWduZXR0ZXMvY29ycnBsb3QtaW50cm8uaHRtbA0KDQpjb3IgPC0gY29yKGRmKQ0KY29yX210ZXN0IDwtIGNvci5tdGVzdChkZiwgY29uZi5sZXZlbCA9IDAuOTkpICMgY29tYmluaW5nIGNvcnJlbG9ncmFtIHdpdGggc2lnbmlmaWNhbmNlIHRlc3QNCmNvcnJwbG90KGNvciwgbWV0aG9kID0gIm51bWJlciIsIG9yZGVyID0gJ2hjbHVzdCcsIGFkZHJlY3QgPSAzLCBwLm1hdCA9IGNvcl9tdGVzdCRwLCBpbnNpZyA9ICJwY2giKSAjIG9yZGVyID0gQU9FLCBGUEMsIGhjbHVzdCArIGFkZHJlY3QNCg0KY29ycnBsb3QoY29yLCBwLm1hdCA9IGNvcl9tdGVzdCRwLCBsb3cgPSBjb3JfbXRlc3QkbG93Q0ksIHVwcCA9IGNvcl9tdGVzdCR1cHBDSSwgb3JkZXIgPSAnaGNsdXN0Jywgc2lnLmxldmVsID0gMC4wMSwgdGwucG9zID0gJ2QnLCBhZGRyZWN0ID0gMywgcmVjdC5jb2wgPSAnbmF2eScsIHBsb3RDID0gJ3JlY3QnLCBjbC5wb3MgPSAnbicsIGluc2lnID0gInAtdmFsdWUiKQ0KYGBgDQoNCi0tLQ0KTG9yZW56IGN1cnZlICYgR2luaSBjb2VmZmljaWVudA0KLS0tDQphIEdpbmkgY29lZmZpY2llbnQgb2YgMC41NiBpcyBxdWl0ZSBoaWdoLCB3aGljaCBtZWFucyB0aGF0IG9ubHkgYSB0b3AgZmV3IHByb2R1Y2VycyBhcmUgcmVzcG9uc2libGUgZm9yIGEgbGFyZ2UgYW1vdW50IG9mIHRoZSB0b3RhbCBTVVAgd2FzdGUgY29udHJpYnV0aW9uLCBpbiBvdGhlciB3b3JkcyAxMCUgYXJlIHJlc3BvbnNpYmxlIGZvciA0NCUgb2YgdGhlIHdhc3RlDQoNCg0KYGBge3J9DQpuYW1lID0gYygndG90YWxfY29udHJpYnV0aW9uX3RvX3N1cF93YXN0ZScsICdQcm9kdWNlcnMnKQ0KZGYgPC0gcGxhc3RpYyAlPiUgcmVuYW1lKHZhbHVlID0gdG90YWxfY29udHJpYnV0aW9uX3RvX3N1cF93YXN0ZSkgJT4lIHNlbGVjdCh2YWx1ZSkgDQoNCmxpYnJhcnkoZ2dsb3JlbnopICN0cmFuc2Zvcm1hdGlvbnMgZm9yIHBsb3R0aW5nIExvcmVueiBjdXJ2ZSwgaHR0cHM6Ly9naXRodWIuY29tL2pqY2hlcm4vZ2dsb3JlbnoNCg0KbG9yZW56Y3VydmUgPC0gZGYgJT4lIA0KICBnZ3Bsb3QoYWVzKHZhbHVlKSkgKw0KICAgIHN0YXRfbG9yZW56KGRlc2MgPSBGQUxTRSkgKw0KICAgIGNvb3JkX2ZpeGVkKCkgKw0KICAgIGdlb21fYWJsaW5lKGxpbmV0eXBlID0gJ2Rhc2hlZCcpICsNCiAgICB0aGVtZV9taW5pbWFsKCkgKw0KICAgIGhyYnJ0aGVtZXM6OnNjYWxlX3hfcGVyY2VudCgpICsNCiAgICBocmJydGhlbWVzOjpzY2FsZV95X3BlcmNlbnQoKSArDQogICAgIyBocmJydGhlbWVzOjp0aGVtZV9pcHN1bV9yYygpICsNCiAgICBhbm5vdGF0ZV9pbmVxKGRmJHZhbHVlKSArDQogICAgZ2d0aXRsZShwYXN0ZSgiTG9yZW56IGN1cnZlIGZvciIsIG5hbWVbMV0sIHNlcD0iICIpKSANCmxvcmVuemN1cnZlIDwtIGdncGxvdGx5KGxvcmVuemN1cnZlKSAlPiUgbGF5b3V0KHlheGlzID0gbGlzdCh0aXRsZSA9IHBhc3RlKCJjdW11bGF0aXZlIHBlcmNlbnRhZ2Ugb2YiLCBuYW1lWzFdLCBzZXA9IiAiKSksIHhheGlzID0gbGlzdCh0aXRsZSA9IHBhc3RlKCJjdW11bGF0aXZlIHBlcmNlbnRhZ2Ugb2YiLCBuYW1lWzJdLCBzZXA9IiAiKSkpIA0KDQpsb3JlbnpjdXJ2ZQ0KYGBgDQpgYGB7cn0NCm5hbWUgPSBjKCdmbGV4aWJsZV9mb3JtYXRfY29udHJpYnV0aW9uX3RvX3N1cF93YXN0ZScsICdyaWdpZF9mb3JtYXRfY29udHJpYnV0aW9uX3RvX3N1cF93YXN0ZScsICdQcm9kdWNlcnMnLCAnZmxleGlibGUnLCAncmlnaWQnKQ0KZGYgPC0gcGxhc3RpYyAlPiUgcmVuYW1lKGZsZXhpYmxlID0gZmxleGlibGVfZm9ybWF0X2NvbnRyaWJ1dGlvbl90b19zdXBfd2FzdGUsIHJpZ2lkID0gcmlnaWRfZm9ybWF0X2NvbnRyaWJ1dGlvbl90b19zdXBfd2FzdGUpICU+JSBzZWxlY3QoZmxleGlibGUsIHJpZ2lkKSAlPiUgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKGZsZXhpYmxlLHJpZ2lkKSkNCg0KbGlicmFyeShnZ2xvcmVueikgI3RyYW5zZm9ybWF0aW9ucyBmb3IgcGxvdHRpbmcgTG9yZW56IGN1cnZlLCBodHRwczovL2dpdGh1Yi5jb20vampjaGVybi9nZ2xvcmVueg0KDQojIGdldCBnZ3Bsb3Qgc3RhbmRhcmQgY29sb3JzIGZvciBncm91cGluZywgd2hpY2ggYXJlIGVxdWFsbHkgc3BhY2VkIGh1ZXMgYXJvdW5kIHRoZSBjb2xvciB3aGVlbCwgc3RhcnRpbmcgZnJvbSAxNQ0KZ2dfY29sb3JfaHVlIDwtIGZ1bmN0aW9uKG4pIHsNCiAgaHVlcyA9IHNlcSgxNSwgMzc1LCBsZW5ndGggPSBuICsgMSkNCiAgaGNsKGggPSBodWVzLCBsID0gNjUsIGMgPSAxMDApWzE6bl0NCn0NCg0KbG9yZW56Y3VydmUgPC0gZGYgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB2YWx1ZSwgY29sb3IgPSBuYW1lKSkgKw0KICAgIHN0YXRfbG9yZW56KGRlc2MgPSBGQUxTRSkgKw0KICAgIGNvb3JkX2ZpeGVkKCkgKw0KICAgIGdlb21fYWJsaW5lKGxpbmV0eXBlID0gJ2Rhc2hlZCcpICsNCiAgICB0aGVtZV9taW5pbWFsKCkgKw0KICAgIGhyYnJ0aGVtZXM6OnNjYWxlX3hfcGVyY2VudCgpICsNCiAgICBocmJydGhlbWVzOjpzY2FsZV95X3BlcmNlbnQoKSArDQogICAgIyBocmJydGhlbWVzOjp0aGVtZV9pcHN1bV9yYygpICsNCiAgICBhbm5vdGF0ZV9pbmVxKGZpbHRlcihkZiwgbmFtZSA9PSBuYW1lWzRdKSR2YWx1ZSwgeSA9IDAuOTUsIGNvbG91ciA9IGdnX2NvbG9yX2h1ZSgyKVsxXSkgKw0KICAgIGFubm90YXRlX2luZXEoZmlsdGVyKGRmLCBuYW1lID09IG5hbWVbNV0pJHZhbHVlLCB5ID0gMC45MCwgY29sb3VyID0gZ2dfY29sb3JfaHVlKDIpWzJdKSArDQogICAgZ2d0aXRsZShwYXN0ZSgiY29tcGFyZSBMb3JlbnogY3VydmUgb2YiLCBuYW1lWzFdLCAiYW5kIiwgbmFtZVsyXSwgc2VwPSIgIikpIA0KbG9yZW56Y3VydmUgPC0gZ2dwbG90bHkobG9yZW56Y3VydmUpICU+JSBsYXlvdXQoeWF4aXMgPSBsaXN0KHRpdGxlID0gcGFzdGUoImN1bXVsYXRpdmUgcGVyY2VudGFnZSBvZjxicj4iLCBuYW1lWzFdLCAiPGJyPiIsIG5hbWVbMl0sIHNlcD0iIikpLCB4YXhpcyA9IGxpc3QodGl0bGUgPSBwYXN0ZSgiY3VtdWxhdGl2ZSBwZXJjZW50YWdlIG9mIiwgbmFtZVsyXSwgc2VwPSIgIikpKSANCg0KbG9yZW56Y3VydmUNCmBgYA0KDQotLS0NCnByaW5jaXBhbCBjb21wb25lbnQgYW5hbHlzaXMgY29sb3JlZCBieSBzZWxmIG9yZ2FuaXppbmcgbWFwIGNsdXN0ZXINCi0tLQ0KSSBuZWVkIG1vcmUga25vd2xlZGdlIGhvdyB0byB3b3JrIHdpdGggYW5kIGludGVycHJldCBTT00gYW5kIFBDQSwgbWF5YmUgYWxzbyBub3QgZW5vdWdoIG9ic2VydmF0aW9ucyBpbiBkYXRhIHNldA0KaHR0cHM6Ly9pYW1jaWVyYS5naXRodWIuaW8vU09NZXhhbXBsZS9odG1sL1NPTV9STkFzZXFfdHV0b3JpYWxfcGFydDJhX1NPTS5odG1sDQoNCmJ1dCB0aGUgY2x1c3RlcmluZyBmcm9tIFNPTSwgd2hlbiBvbmx5IGluZGVwZW5kZW50IHZhcmlhYmxlcyBhcmUgdXNlZCwgYWN0dWFsbHkgc2hvdyB0aGUgdHdvIGFzc3VtZWQgc3VicG9wdWxhdGlvbnMgYmFzZWQgb24gcmlnaWQgdnMgZmxleGlibGUgcHJvZHVjaXRvbg0KDQpgYGB7cn0NCm5hbWUgPSBjKCdwb2x5bWVyX3Byb2R1Y2VyJykNCmRmIDwtIHBsYXN0aWMgJT4lIHNlbGVjdCgtIHRvdGFsX3dhc3RlX2Rpdl9wcm9kdWN0aW9uLCAtcmFuaywgLW5vX29mX2Fzc2V0cywgLXRvdGFsX2NvbnRyaWJ1dGlvbl90b19zdXBfd2FzdGUpICU+JSAjIHJlbW92ZWQgdmFyaWFibGVzIHdoaWNoIGFyZSBkZXBlbmRlZCBvbiBlYWNoIG90aGVyDQogIHJlbmFtZSggcHJvZHVjdCA9IHByb2R1Y3Rpb25fb2ZfaW5fc2NvcGVfcG9seW1lcnMsIA0KICAgICAgICAgIGZsZXhpYmxlID0gZmxleGlibGVfZm9ybWF0X2NvbnRyaWJ1dGlvbl90b19zdXBfd2FzdGUsIA0KICAgICAgICAgIHJpZ2lkID0gcmlnaWRfZm9ybWF0X2NvbnRyaWJ1dGlvbl90b19zdXBfd2FzdGUpDQoNCg0KbGlicmFyeShrb2hvbmVuKSAjIGZ1bmN0aW9ucyB0byB0cmFpbiBzZWxmLW9yZ2FuaXNpbmcgbWFwcyAoU09NcykNCg0KIyBzZXR1cCBmb3IgcGNhDQpzY2FsZV9kYXRhIDwtIGFzLm1hdHJpeCh0KHNjYWxlKHQoZGZbLCAhbmFtZXMoZGYpICVpbiUgbmFtZV0pKSkpICMgV2UgbmVlZCB0byBub3JtYWxpemUgdGhlIGRhdGEgYmFzZWQgb24gc2NhbGUgZnVuY3Rpb24gYmVjYXVzZSB0aGUgdmFyaWFibGVzIGFyZSBkaWZmZXJlbnQgc2NhbGVzOyBOb3JtYWxpemF0aW9uIG1lYW5zIHN1YnRyYWN0aW5nIG1lYW4gZnJvbSBlYWNoIG9ic2VydmF0aW9uIGFuZCBkaXZpZGluZyB3aXRoIHN0YW5kYXJkIGRldmlhdGlvbi4gQ2hlY2sgYWxsIHRoZSB2YXJpYWJsZXMgbWVhbiB2YWx1ZXMgYXJlIHplcm8gbm93DQpoZWFkKHNjYWxlX2RhdGEpDQoNCiMgcHJpbmNpcGxlIGNvbXBvbmVudCBhbmFseXNpcw0KcGNhIDwtIHByY29tcChzY2FsZV9kYXRhLCBzY2FsZT1UUlVFKQ0Kc3VtbWFyeShwY2EpDQoNCiMgdmlzdWFsaXplIHBjcyByZXN1bHRzDQojIENvbnRyaWJ1dGlvbnMgb2YgdmFyaWFibGVzIHRvIFBDMQ0KZnZpel9jb250cmliKHBjYSwgY2hvaWNlID0gInZhciIsIGF4ZXMgPSAxLCB0b3AgPSAxMCkNCiMgQ29udHJpYnV0aW9ucyBvZiB2YXJpYWJsZXMgdG8gUEMyDQpmdml6X2NvbnRyaWIocGNhLCBjaG9pY2UgPSAidmFyIiwgYXhlcyA9IDIsIHRvcCA9IDEwKQ0KIyBDb250cm9sIHZhcmlhYmxlIGNvbG9ycyB1c2luZyB0aGVpciBjb250cmlidXRpb25zIHRvIHRoZSBwcmluY2lwbGUgYXhpcw0KZnZpel9wY2FfdmFyKHBjYSwgY29sLnZhcj0iY29udHJpYiIsDQogICAgICAgICAgICAgZ3JhZGllbnQuY29scyA9IGMoIiMwMEFGQkIiLCAiI0U3QjgwMCIsICIjRkM0RTA3IiksDQogICAgICAgICAgICAgcmVwZWwgPSBUUlVFICMgQXZvaWQgdGV4dCBvdmVybGFwcGluZw0KICAgICAgICAgICAgICkgKyB0aGVtZV9taW5pbWFsKCkgKyBnZ3RpdGxlKCJWYXJpYWJsZXMgLSBQQ0EiKQ0KDQojIGFkZCBiYWNrIHRvIG9yaWdpbmFsIHNvIGV2ZXJ5dGhpbmcgaXMgdG9nZXRoZXINCnBjYV9zY29yZXMgPC0gZGF0YS5mcmFtZShwY2EkeCkNCmRhdGFfdmFsIDwtIGNiaW5kKGRmLCBwY2Ffc2NvcmVzKQ0KDQpwY2FfcGxvdCA8LSBnZ3Bsb3QoZGF0YV92YWwsIGFlcyh4ID0gUEMxLCB5ID0gUEMyKSkgKw0KICAgIGdlb21fcnVnKGFscGhhID0gMC41KSArICMgdHdvIDFkIG1hcmdpbmFsIGRpc3RyaWJ1dGlvbnMsIGRpc3BsYXkgaW5kaXZpZHVhbCBjYXNlcyBzbyBhcmUgYmVzdCB1c2VkIHdpdGggc21hbGxlciBkYXRhc2V0cw0KICAgIGdlb21fZGVuc2l0eV8yZChhbHBoYSA9IDAuMiwgYmlucyA9IDQpICsjIDJEIGtlcm5lbCBkZW5zaXR5IGVzdGltYXRpb24gdXNpbmcgTUFTUzo6a2RlMmQoKSBhbmQgZGlzcGxheSB0aGUgcmVzdWx0cyB3aXRoIGNvbnRvdXJzDQogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNzUpICsgIyBwb2ludCBnZW9tIGlzIHVzZWQgdG8gY3JlYXRlIHNjYXR0ZXJwbG90cw0KICAgIHRoZW1lX21pbmltYWwoKQ0KcGNhX3Bsb3QgPC0gZ2dwbG90bHkocGNhX3Bsb3QpICU+JSBsYXlvdXQoKQ0KDQpwY2FfcGxvdA0KDQojIGNsdXN0ZXJpbmcgaXMgcGVyZm9ybWVkIHVzaW5nIHRoZSBzb20oKSBmdW5jdGlvbiBvbiB0aGUgc2NhbGVkIGdlbmUgZXhwcmVzc2lvbiB2YWx1ZXMuDQpzZXQuc2VlZCgzKQ0KDQojIGRlZmluZSBhIGdyaWQgZm9yIHRoZSBTT00gYW5kIHRyYWluDQpncmlkX3NpemUgPC0gbmNvbChzY2FsZV9kYXRhKQ0Kc29tX2dyaWQgPC0gc29tZ3JpZCh4ZGltID0gZ3JpZF9zaXplLCB5ZGltID0gZ3JpZF9zaXplLCB0b3BvID0gJ2hleGFnb25hbCcpDQpzb21fbW9kZWwgPC0gc29tKHNjYWxlX2RhdGEsIGdyaWQgPSBzb21fZ3JpZCkNCnN1bW1hcnkoc29tX21vZGVsKQ0KDQojIGdlbmVyYXRlIHNvbSBwbG90cyBhZnRlciB0cmFpbmluZw0KcGxvdChzb21fbW9kZWwsIHR5cGUgPSAnbWFwcGluZycpDQpwbG90KHNvbV9tb2RlbCwgdHlwZSA9ICdjb2RlcycpDQojIHBsb3Qoc29tX21vZGVsLCB0eXBlID0gJ2NvdW50cycpDQojIHBsb3Qoc29tX21vZGVsLCB0eXBlID0gJ2Rpc3QubmVpZ2hib3VycycpDQojIHBsb3Qoc29tX21vZGVsLCB0eXBlID0gJ3F1YWxpdHknKQ0KIyBwbG90KHNvbV9tb2RlbCwgdHlwZSA9ICdjaGFuZ2VzJykNCg0KIyBmdXJ0aGVyIHNwbGl0IHRoZSBjbHVzdGVycyBpbnRvIGEgc21hbGxlciBzZXQgb2YgY2x1c3RlcnMgdXNpbmcgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcuDQpzb21fY2x1c3RlciA8LSBjdXRyZWUoaGNsdXN0KGRpc3Qoc29tX21vZGVsJGNvZGVzW1sxXV0pKSwgMikgIyB1c2UgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgdG8gY2x1c3RlciB0aGUgY29kZWJvb2sgdmVjdG9ycw0KDQpwbG90KHNvbV9tb2RlbCwgdHlwZT0ibWFwcGluZyIsIGJnY29sID0gc29tX2NsdXN0ZXIsIG1haW4gPSAiQ2x1c3RlcnMiKQ0KYWRkLmNsdXN0ZXIuYm91bmRhcmllcyhzb21fbW9kZWwsIHNvbV9jbHVzdGVyKQ0KDQojIGF0dGFjaCB0aGUgaGllcmNoYWwgY2x1c3RlciB0byB0aGUgbGFyZ2VyIGRhdGFzZXQgZGF0YV92YWwuDQpncmlkU3F1YXJlIDwtIGdyaWRfc2l6ZSAqIGdyaWRfc2l6ZQ0Kc29tX2NsdXN0ZXJLZXkgPC0gZGF0YS5mcmFtZShzb21fY2x1c3RlcikNCnNvbV9jbHVzdGVyS2V5JHVuaXRfY2xhc3NpZiA8LSBjKDE6Z3JpZFNxdWFyZSkNCmRhdGFfdmFsIDwtIGNiaW5kKGRhdGFfdmFsLHNvbV9tb2RlbCR1bml0LmNsYXNzaWYsc29tX21vZGVsJGRpc3RhbmNlcykgJT4lIHJlbmFtZSh1bml0X2NsYXNzaWYgPSAnc29tX21vZGVsJHVuaXQuY2xhc3NpZicsIGRpc3RhbmNlcyA9ICdzb21fbW9kZWwkZGlzdGFuY2VzJykNCmRhdGFfdmFsIDwtIG1lcmdlKGRhdGFfdmFsLCBzb21fY2x1c3RlcktleSwgYnkueCA9ICJ1bml0X2NsYXNzaWYiICkNCmhlYWQoZGF0YV92YWwpDQoNCiMgcGxvdCBwY2Egd2l0aCBjb2xvcmVkIGNsdXN0ZXJzDQpwY2Fzb21fcGxvdCA8LSBnZ3Bsb3QoZGF0YV92YWwsIGFlcyh4ID0gUEMxLCB5ID0gUEMyLCBjb2xvciA9IGZhY3Rvcihzb21fY2x1c3RlciksIHRleHQgPSBwb2x5bWVyX3Byb2R1Y2VyKSkgKw0KICAgIGdlb21fcnVnKGFscGhhID0gMC41KSArICMgdHdvIDFkIG1hcmdpbmFsIGRpc3RyaWJ1dGlvbnMsIGRpc3BsYXkgaW5kaXZpZHVhbCBjYXNlcyBzbyBhcmUgYmVzdCB1c2VkIHdpdGggc21hbGxlciBkYXRhc2V0cw0KICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjc1KSArICMgcG9pbnQgZ2VvbSBpcyB1c2VkIHRvIGNyZWF0ZSBzY2F0dGVycGxvdHMNCiAgICB0aGVtZV9taW5pbWFsKCkNCnBjYXNvbV9wbG90IDwtIGdncGxvdGx5KHBjYXNvbV9wbG90KSAlPiUgbGF5b3V0KCkNCg0KcGNhc29tX3Bsb3QNCmBgYA0KDQpgYGB7cn0NCiMgdHdvIHZhcmlhYmxlcywgY29udGludW91cyB4LCBjb250aW51b3VzIHksIHNob3cgdHJlbmQgYW5kIGRpc3RyaWJ1dGlvbg0KbmFtZSA9IGMoJ3Byb2R1Y3Rpb25fb2ZfaW5fc2NvcGVfcG9seW1lcnMnLCAndG90YWxfY29udHJpYnV0aW9uX3RvX3N1cF93YXN0ZScpDQpkZiA8LSBtZXJnZShwbGFzdGljLCBkYXRhX3ZhbCwgYnkueCA9ICdwb2x5bWVyX3Byb2R1Y2VyJykNCmRmIDwtIGRmICU+JSByZW5hbWUoeCA9IHByb2R1Y3Rpb25fb2ZfaW5fc2NvcGVfcG9seW1lcnMsIHkgPSB0b3RhbF9jb250cmlidXRpb25fdG9fc3VwX3dhc3RlLCBjbHVzdGVyID0gc29tX2NsdXN0ZXIsIHRleHQgPSBwb2x5bWVyX3Byb2R1Y2VyKSAlPiUgc2VsZWN0KHgsIHksIGNsdXN0ZXIsIHRleHQpIA0KDQojIGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9nZW9tX3Ntb290aC5odG1sDQpwb2ludF9wbG90IDwtIGRmICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSB4LCB5ID0geSwgY29sb3IgPSBmYWN0b3IoY2x1c3RlcikpKSArDQogICAgIyBnZW9tX2ppdHRlcihhbHBoYSA9IDAuNSwgc2l6ZSA9IDEpICsNCiAgICBnZW9tX3J1ZyhhbHBoYSA9IDAuNSkgKyAjIHR3byAxZCBtYXJnaW5hbCBkaXN0cmlidXRpb25zLCBkaXNwbGF5IGluZGl2aWR1YWwgY2FzZXMgc28gYXJlIGJlc3QgdXNlZCB3aXRoIHNtYWxsZXIgZGF0YXNldHMNCiAgICBnZW9tX2RlbnNpdHlfMmQoYWxwaGEgPSAwLjIsIGJpbnMgPSA0KSArIyAyRCBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0aW9uIHVzaW5nIE1BU1M6OmtkZTJkKCkgYW5kIGRpc3BsYXkgdGhlIHJlc3VsdHMgd2l0aCBjb250b3Vycw0KICAgIGdlb21fc21vb3RoKGZpbGwgPSAiZ3JleTkwIikgKyAjIGFpZHMgdGhlIGV5ZSBpbiBzZWVpbmcgcGF0dGVybnMgaW4gdGhlIHByZXNlbmNlIG9mIG92ZXJwbG90dGluZw0KICAgIGdlb21fcG9pbnQoYWVzKHRleHQgPSB0ZXh0KSwgYWxwaGEgPSAwLjc1KSArICMgcG9pbnQgZ2VvbSBpcyB1c2VkIHRvIGNyZWF0ZSBzY2F0dGVycGxvdHMNCiAgICB0aGVtZV9taW5pbWFsKCkgKw0KICAgIGdndGl0bGUocGFzdGUoInRyZW5kIG9mIiwgbmFtZVsyXSwgIm92ZXIiLCBuYW1lWzFdLCBzZXA9IiAiKSkgDQpwb2ludF9wbG90IDwtIGdncGxvdGx5KHBvaW50X3Bsb3QpICU+JSBsYXlvdXQoeGF4aXMgPSBsaXN0KHNob3d0aWNrbGFiZWxzID0gRkFMU0UpKQ0KDQp4X2RlbnNpdHlfcGxvdCA8LSBkZiAlPiUNCiAgZ2dwbG90KGFlcyh4ID0geCwgY29sb3IgPSBmYWN0b3IoY2x1c3RlcikpKSArDQogICAgc3RhdF9kZW5zaXR5KGdlb209ImxpbmUiKSArICMgZHJhd3Mga2VybmVsIGRlbnNpdHkgZXN0aW1hdGUsIHdoaWNoIGlzIGEgc21vb3RoZWQgdmVyc2lvbiBvZiB0aGUgaGlzdG9ncmFtDQogICAgIyBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEpICsNCiAgICB0aGVtZV9taW5pbWFsKCkgDQp4X2RlbnNpdHlfcGxvdCA8LSBnZ3Bsb3RseSh4X2RlbnNpdHlfcGxvdCkgJT4lIGxheW91dCh5YXhpcyA9IGxpc3Qoc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSwgc2hvd2dyaWQgPSBGQUxTRSksIHhheGlzID0gbGlzdChzaG93dGlja2xhYmVscyA9IEZBTFNFLCBzaG93Z3JpZCA9IEZBTFNFKSkNCg0KeV9kZW5zaXR5X3Bsb3QgPC0gZGYgJT4lDQogIGdncGxvdChhZXMoeCA9IHksIGNvbG9yID0gZmFjdG9yKGNsdXN0ZXIpKSkgKw0KICAgIHN0YXRfZGVuc2l0eShnZW9tPSJsaW5lIikgKyAjIGRyYXdzIGtlcm5lbCBkZW5zaXR5IGVzdGltYXRlLCB3aGljaCBpcyBhIHNtb290aGVkIHZlcnNpb24gb2YgdGhlIGhpc3RvZ3JhbQ0KICAgICMgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxKSArDQogICAgY29vcmRfZmxpcCgpICsNCiAgICB0aGVtZV9taW5pbWFsKCkgDQp5X2RlbnNpdHlfcGxvdCA8LSBnZ3Bsb3RseSh5X2RlbnNpdHlfcGxvdCkgJT4lIGxheW91dCh5YXhpcyA9IGxpc3Qoc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSwgc2hvd2dyaWQgPSBGQUxTRSksIHhheGlzID0gbGlzdChzaG93dGlja2xhYmVscyA9IEZBTFNFLCBzaG93Z3JpZCA9IEZBTFNFKSkNCg0KIyBodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvZ2VvbV9xdWFudGlsZS5odG1sDQpxdWFsdGlsZV9wbG90IDwtIGRmICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSB4LCB5ID0geSwgY29sb3IgPSBmYWN0b3IoY2x1c3RlcikpKSArDQogICAgZ2VvbV9xdWFudGlsZShhbHBoYSA9IDAuOCkgKyAjIGZpdHMgYSBxdWFudGlsZSByZWdyZXNzaW9uIHRvIHRoZSBkYXRhIGFuZCBkcmF3cyB0aGUgZml0dGVkIHF1YW50aWxlcyB3aXRoIGxpbmVzDQogICAgdGhlbWVfbWluaW1hbCgpIA0KcXVhbHRpbGVfcGxvdCA8LSBnZ3Bsb3RseShxdWFsdGlsZV9wbG90KSAlPiUgbGF5b3V0KHlheGlzID0gbGlzdChzaG93dGlja2xhYmVscyA9IEZBTFNFLCBzaG93Z3JpZCA9IEZBTFNFKSkNCg0KIyBtZXJnZSBmaWd1cmVzIGludG8gb25lIHBsb3QsIHZpYSBzdWJwbG90cywgaHR0cHM6Ly9wbG90bHktci5jb20vYXJyYW5naW5nLXZpZXdzLmh0bWwNCnN1YjEgPC0gc3VicGxvdCh4X2RlbnNpdHlfcGxvdCwgcGxvdGx5X2VtcHR5KCksIHBvaW50X3Bsb3QsIHlfZGVuc2l0eV9wbG90LCBucm93cyA9IDIsIG1hcmdpbiA9IDAsIGhlaWdodHMgPSBjKDAuMTUsIDAuODUpLCB3aWR0aHMgPSBjKDAuOSwgMC4xKSwgc2hhcmVYID0gVFJVRSwgc2hhcmVZID0gVFJVRSwgdGl0bGVYID0gRkFMU0UsIHRpdGxlWSA9IEZBTFNFKSAlPiUgbGF5b3V0KCkNCnN1YjIgPC0gc3VicGxvdChxdWFsdGlsZV9wbG90LCBwbG90bHlfZW1wdHkoKSwgbWFyZ2luID0gMCwgd2lkdGhzID0gYygwLjksIDAuMTApLCB0aXRsZVggPSBGQUxTRSwgdGl0bGVZID0gRkFMU0UpICU+JSBsYXlvdXQoKQ0KZmlnIDwtIHN1YnBsb3Qoc3ViMSwgc3ViMiwgbnJvd3MgPSAyLCBtYXJnaW4gPSAwLCBoZWlnaHRzID0gYygwLjgsIDAuMiksIHNoYXJlWCA9IFRSVUUpICU+JSBsYXlvdXQoeGF4aXMgPSBsaXN0KHRpdGxlID0gbmFtZVsxXSksIHlheGlzID0gbGlzdCh0aXRsZSA9IG5hbWVbMl0pKQ0KICANCmZpZw0KYGBgDQoNCi0tLQ0KcGFyYWxsZWwgY29vcmRpbmF0ZSBwbG90DQotLS0NCnBhcmFsbGVsIGNvb3JkaW5hdGUgcGxvdCBpcyBub3QgdmVyeSBpbnNpZ2h0ZnVsLCBzaW5jZSB0aGVyZSBpcyBzbyBtdWNoIGRhdGEgY2xvc2UgdG9nZXRoZXIsIHdlIGNhbm5vdCBzZWUgYW55IGdyb3VwaW5nIHdpdGhvdXQgcHJpb3Iga25vd2xlZGdlLCBvbmx5IGhpbnQgaXMgd2hlbiB5b3Ugem9vbSBpbiBhdCB0aGUgYnVsayBvZiB0aGUgZGF0YSBpbiB0aGUgbG93ZXIgdGhpcmQgKHkgZGlyZWN0aW9uKSwgdGhlcmUgaXMgYSBuZWdhdGl2ZSBjb3JyZWxhdGlvbg0KDQpgYGB7cn0NCm5hbWUgPSBjKCdwbGFzdGljIHByb2R1Y2VycyBjbHVzdGVyZWQgYnkgZm9jdXMnKQ0KZGYgPC0gbWVyZ2UocGxhc3RpYywgc2VsZWN0KGRhdGFfdmFsLCBzb21fY2x1c3RlciwgcG9seW1lcl9wcm9kdWNlciksIGJ5LnggPSAncG9seW1lcl9wcm9kdWNlcicpICU+JQ0KICBtdXRhdGUoc29tX2NsdXN0ZXIgPSBhcy5jaGFyYWN0ZXIoc29tX2NsdXN0ZXIpKSAlPiUNCiAgcmVuYW1lKHByb2R1Y3QgPSBwcm9kdWN0aW9uX29mX2luX3Njb3BlX3BvbHltZXJzLCANCiAgICAgICAgICBmbGV4aWJsZSA9IGZsZXhpYmxlX2Zvcm1hdF9jb250cmlidXRpb25fdG9fc3VwX3dhc3RlLCANCiAgICAgICAgICByaWdpZCA9IHJpZ2lkX2Zvcm1hdF9jb250cmlidXRpb25fdG9fc3VwX3dhc3RlLA0KICAgICAgICAgIGFzc2V0cyA9ICBub19vZl9hc3NldHMsDQogICAgICAgICAgY2x1c3RlciA9IHNvbV9jbHVzdGVyKSAlPiUgDQogIHNlbGVjdChhc3NldHMsIHByb2R1Y3QsIGZsZXhpYmxlLCByaWdpZCwgY2x1c3RlcikgDQoNCmxpYnJhcnkoR0dhbGx5KSAjIGV4dGVuZHMgZ2dwbG90MiBieSBhZGRpbmcgc2V2ZXJhbCBmdW5jdGlvbnMgdG8gcmVkdWNlIHRoZSBjb21wbGV4aXR5IG9mIGNvbWJpbmluZyBnZW9tcyB3aXRoIHRyYW5zZm9ybWVkIGRhdGENCg0KIyBodHRwczovL3d3dy5yLWdyYXBoLWdhbGxlcnkuY29tL3BhcmFsbGVsLXBsb3QtZ2dhbGx5Lmh0bWwjY3VzdG9tDQpwYXJjb29yZF9wbG90IDwtIGdncGFyY29vcmQoZGYsDQogICAgICAgICAgIGNvbHVtbnMgPSAxOjQsIGdyb3VwQ29sdW1uID0gNSwNCiAgICAgICAgICAgc2NhbGU9J2NlbnRlcicsICMgc2NhbGluZzogc3RhbmRhcmRpemUgYW5kIGNlbnRlciB2YXJpYWJsZXMNCiAgICAgICAgICAgc2hvd1BvaW50cyA9IFRSVUUsDQogICAgICAgICAgIHRpdGxlID0gbmFtZSwNCiAgICAgICAgICAgYWxwaGFMaW5lcyA9IDAuMykgKw0KICB0aGVtZV9taW5pbWFsKCkgDQpwYXJjb29yZF9wbG90IDwtIGdncGxvdGx5KHBhcmNvb3JkX3Bsb3QpICU+JSBsYXlvdXQoYXV0b3NpemU9VCkNCg0KcGFyY29vcmRfcGxvdA0KYGBgDQoNCi0tLQ0Kay1tZWFucyBjbHVzdGVyaW5nDQotLS0NCmh0dHBzOi8vc3RhdHNhbmRyLmNvbS9ibG9nL2NsdXN0ZXJpbmctYW5hbHlzaXMtay1tZWFucy1hbmQtaGllcmFyY2hpY2FsLWNsdXN0ZXJpbmctYnktaGFuZC1hbmQtaW4tci8jb3B0aW1hbC1udW1iZXItb2YtY2x1c3RlcnMNCkFzIGEgcmVtaW5kZXIsIHRoaXMgbWV0aG9kIGFpbXMgYXQgcGFydGl0aW9uaW5nIG4gb2JzZXJ2YXRpb25zIGludG8gayBjbHVzdGVycyBpbiB3aGljaCBlYWNoIG9ic2VydmF0aW9uIGJlbG9uZ3MgdG8gdGhlIGNsdXN0ZXIgd2l0aCB0aGUgY2xvc2VzdCBhdmVyYWdlLCBzZXJ2aW5nIGFzIGEgcHJvdG90eXBlIG9mIHRoZSBjbHVzdGVyDQp3b3JrcyBub3QgcXVpdGUgYXMgd2VsbCBhcyBTT00sIGJ1dCB2ZXJ5IGNsb3NlLCBhbHNvIHJlY29tbWVuZCBjbHVzdGVyIGlzIHRocmVlLCBidXQgY2xvc2VseSBmb2xsb3dlZCBieSAyLCBzaW5jZSAzIGRvZXMgbm90IHJldmVhbCBhbnkgc2lnbmlmaWNhbnQgY29ubmVjdGlvbiByaWdodCBub3csIEkgd29uZGVyIHdoYXQgdGhhdCBzaG93cw0KDQpgYGB7cn0NCm5hbWUgPSBjKCdwb2x5bWVyX3Byb2R1Y2VyJykNCmRmIDwtIHBsYXN0aWMgJT4lIHNlbGVjdCgtIHRvdGFsX3dhc3RlX2Rpdl9wcm9kdWN0aW9uLCAtcmFuaywgLW5vX29mX2Fzc2V0cywgLXRvdGFsX2NvbnRyaWJ1dGlvbl90b19zdXBfd2FzdGUpICU+JSAjIHJlbW92ZWQgdmFyaWFibGVzIHdoaWNoIGFyZSBkZXBlbmRlZCBvbiBlYWNoIG90aGVyDQogIHJlbmFtZSggcHJvZHVjdCA9IHByb2R1Y3Rpb25fb2ZfaW5fc2NvcGVfcG9seW1lcnMsIA0KICAgICAgICAgIGZsZXhpYmxlID0gZmxleGlibGVfZm9ybWF0X2NvbnRyaWJ1dGlvbl90b19zdXBfd2FzdGUsIA0KICAgICAgICAgIHJpZ2lkID0gcmlnaWRfZm9ybWF0X2NvbnRyaWJ1dGlvbl90b19zdXBfd2FzdGUpDQoNCmxpYnJhcnkoTmJDbHVzdCkgIyBkZXRlcm1pbmluZyB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMgaW4gYSBkYXRhIHNldA0KbGlicmFyeShjbHVzdGVyKSAjIGNvbXB1dGVzIGFnZ2xvbWVyYXRpdmUgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgb2YgdGhlIGRhdGFzZXQNCg0Kc2NhbGVfZGF0YSA8LSBhcy5tYXRyaXgodChzY2FsZSh0KGRmWywgIW5hbWVzKGRmKSAlaW4lIG5hbWVdKSkpKSAjIFdlIG5lZWQgdG8gbm9ybWFsaXplIHRoZSBkYXRhIGJhc2VkIG9uIHNjYWxlIGZ1bmN0aW9uIGJlY2F1c2UgdGhlIHZhcmlhYmxlcyBhcmUgZGlmZmVyZW50IHNjYWxlczsgTm9ybWFsaXphdGlvbiBtZWFucyBzdWJ0cmFjdGluZyBtZWFuIGZyb20gZWFjaCBvYnNlcnZhdGlvbiBhbmQgZGl2aWRpbmcgd2l0aCBzdGFuZGFyZCBkZXZpYXRpb24uIENoZWNrIGFsbCB0aGUgdmFyaWFibGVzIG1lYW4gdmFsdWVzIGFyZSB6ZXJvIG5vdw0KaGVhZChzY2FsZV9kYXRhKQ0KDQprbWVhbnNfbW9kZWwgPC0ga21lYW5zKHNjYWxlX2RhdGEsIGNlbnRlcnMgPSAyLCBuc3RhcnQgPSAxMikgIyAgay1tZWFucyBjbHVzdGVyaW5nIGlzIGRvbmUgdmlhIHRoZSBrbWVhbnMoKSBmdW5jdGlvbiwgd2l0aCB0aGUgYXJndW1lbnQgY2VudGVycyB0aGF0IGNvcnJlc3BvbmRzIHRvIHRoZSBudW1iZXIgb2YgZGVzaXJlZCBjbHVzdGVycw0KZGZfY2x1c3RlciA8LSB0aWJibGUoZGYsIGNsdXN0ZXIgPSBhcy5mYWN0b3Ioa21lYW5zX21vZGVsJGNsdXN0ZXIpKSAjIHN0b3JlIGNsdXN0ZXIgaW4gb3JpZ2luYWwgZGF0YSBzZXQgYXMgY29sdW1uDQpoZWFkKGRmX2NsdXN0ZXIpDQoNCiMgY2hlY2sgcXVhbGl0eSBvZiBhIGstbWVhbnMgcGFydGl0aW9uDQpxdWFsaXR5IDwtIGttZWFuc19tb2RlbCRiZXR3ZWVuc3MgLyBrbWVhbnNfbW9kZWwkdG90c3MgDQpwcmludChwYXN0ZSgicXVhbGl0eSBvZiBrbWVhbnMgaXMgQlNTL1RTUzogIiwgZm9ybWF0KHJvdW5kKHF1YWxpdHksMiksIG5zbWFsbCA9IDIpKSkNCg0KIyBmaW5kIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzDQpmdml6X25iY2x1c3Qoc2NhbGVfZGF0YSwga21lYW5zLCBtZXRob2QgPSAnd3NzJykgKyAjIEVsYm93IG1ldGhvZCwgbmVlZHMgc2NhbGVkIGRhdGENCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMiwgbGluZXR5cGUgPSAyKSArICMgYWRkIGxpbmUgZm9yIGJldHRlciB2aXN1YWxpemF0aW9uDQogIGxhYnMoc3VidGl0bGUgPSAiRWxib3cgbWV0aG9kIikgIyBhZGQgc3VidGl0bGUNCg0KZnZpel9uYmNsdXN0KHNjYWxlX2RhdGEsIGttZWFucywgbWV0aG9kID0gJ3NpbGhvdWV0dGUnKSArICMgU2lsaG91ZXR0ZSBtZXRob2QsIG5lZWRzIHNjYWxlZCBkYXRhDQogIGxhYnMoc3VidGl0bGUgPSAiU2lsaG91ZXR0ZSBtZXRob2QiKSAjIGFkZCBzdWJ0aXRsZQ0KDQpmdml6X25iY2x1c3QoZGZbLCAhbmFtZXMoZGYpICVpbiUgbmFtZV0sIGttZWFucywgIyBHYXAgc3RhdGlzdGljcywgbmVlZHMgb3JpZ2luYWwgZGF0YSA/DQogICAgICAgICAgICAgbnN0YXJ0ID0gMjUsDQogICAgICAgICAgICAgbWV0aG9kID0gJ2dhcF9zdGF0JywNCiAgICAgICAgICAgICBuYm9vdCA9IDEwMCkgKyAjIHJlZHVjZSBpdCBmb3IgbG93ZXIgY29tcHV0YXRpb24gdGltZSwgYnV0IGxlc3MgcHJlY2lzZSByZXN1bHRzDQogIGxhYnMoc3VidGl0bGUgPSAiR2FwIHN0YXRpc3RpY3MgbWV0aG9kIikNCg0KbmJjbHVzdF9vdXQgPC0gTmJDbHVzdChkYXRhID0gZGZbLCAhbmFtZXMoZGYpICVpbiUgbmFtZV0sICMgTmJDbHVzdCBuZWVkcyB0aGUgb3JpZ2luYWwgZGF0YSA/DQogICAgICAgICAgICAgICAgICAgICAgIGRpc3RhbmNlID0gJ2V1Y2xpZGVhbicsDQogICAgICAgICAgICAgICAgICAgICAgIG1pbi5uYyA9IDIsICMgbWluaW11bSBudW1iZXIgb2YgY2x1c3RlcnMNCiAgICAgICAgICAgICAgICAgICAgICAgbWF4Lm5jID0gMTAsICMgbWF4aW11bSBudW1iZXIgb2YgY2x1c3Rlcg0KICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAnY29tcGxldGUnLA0KICAgICAgICAgICAgICAgICAgICAgICBpbmRleCA9ICdhbGwnKQ0KZnZpel9uYmNsdXN0KG5iY2x1c3Rfb3V0KSArIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnMoc3VidGl0bGUgPSAiTmJDbHVzdCByZXN1bHRzIikNCg0KIyBjaGVjayBxdWFsaXR5IG9mIGNsdXN0ZXJpbmcNCiMgaWYgYSBsYXJnZSBtYWpvcml0eSBvZiB0aGUgc2lsaG91ZXR0ZSBjb2VmZmljaWVudHMgYXJlIHBvc2l0aXZlLCBpdCBpbmRpY2F0ZXMgdGhhdCB0aGUgb2JzZXJ2YXRpb25zIGFyZSBwbGFjZWQgaW4gdGhlIGNvcnJlY3QgZ3JvdXANCnNpbCA8LSBzaWxob3VldHRlKGttZWFuc19tb2RlbCRjbHVzdGVyLCBkaXN0KHNjYWxlX2RhdGEpKSANCmZ2aXpfc2lsaG91ZXR0ZShzaWwpDQoNCmZ2aXpfY2x1c3RlcihrbWVhbnNfbW9kZWwsIGRmWywgIW5hbWVzKGRmKSAlaW4lIG5hbWVdLCBlbGxpcHNlLnR5cGUgPSAnbm9ybScpICsgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3J9DQpuYW1lID0gYygncG9seW1lcl9wcm9kdWNlcicpDQpkZiA8LSBwbGFzdGljICU+JSBzZWxlY3QoLSB0b3RhbF93YXN0ZV9kaXZfcHJvZHVjdGlvbiwgLXJhbmssIC1ub19vZl9hc3NldHMsIC10b3RhbF9jb250cmlidXRpb25fdG9fc3VwX3dhc3RlKSAlPiUgIyByZW1vdmVkIHZhcmlhYmxlcyB3aGljaCBhcmUgZGVwZW5kZWQgb24gZWFjaCBvdGhlcg0KICByZW5hbWUoIHByb2R1Y3QgPSBwcm9kdWN0aW9uX29mX2luX3Njb3BlX3BvbHltZXJzLCANCiAgICAgICAgICBmbGV4aWJsZSA9IGZsZXhpYmxlX2Zvcm1hdF9jb250cmlidXRpb25fdG9fc3VwX3dhc3RlLCANCiAgICAgICAgICByaWdpZCA9IHJpZ2lkX2Zvcm1hdF9jb250cmlidXRpb25fdG9fc3VwX3dhc3RlKQ0KDQpzY2FsZV9kYXRhIDwtIGFzLm1hdHJpeCh0KHNjYWxlKHQoZGZbLCAhbmFtZXMoZGYpICVpbiUgbmFtZV0pKSkpICMgV2UgbmVlZCB0byBub3JtYWxpemUgdGhlIGRhdGEgYmFzZWQgb24gc2NhbGUgZnVuY3Rpb24gYmVjYXVzZSB0aGUgdmFyaWFibGVzIGFyZSBkaWZmZXJlbnQgc2NhbGVzOyBOb3JtYWxpemF0aW9uIG1lYW5zIHN1YnRyYWN0aW5nIG1lYW4gZnJvbSBlYWNoIG9ic2VydmF0aW9uIGFuZCBkaXZpZGluZyB3aXRoIHN0YW5kYXJkIGRldmlhdGlvbi4gQ2hlY2sgYWxsIHRoZSB2YXJpYWJsZXMgbWVhbiB2YWx1ZXMgYXJlIHplcm8gbm93DQoNCmttZWFuX2NhbGMgPC0gZnVuY3Rpb24oZGYsIC4uLil7DQogIGttZWFucyhkZiwgc2NhbGVkID0gLi4uLCBuc3RhcnQgPSAzMCkNCn0NCg0Ka20yIDwtIGttZWFuX2NhbGMoc2NhbGVfZGF0YSwgMikNCmttMyA8LSBrbWVhbl9jYWxjKHNjYWxlX2RhdGEsIDMpDQprbTQgPC0ga21lYW5zKHNjYWxlX2RhdGEsIDQpDQprbTUgPC0ga21lYW5zKHNjYWxlX2RhdGEsIDUpDQprbTYgPC0ga21lYW5zKHNjYWxlX2RhdGEsIDYpDQprbTcgPC0ga21lYW5zKHNjYWxlX2RhdGEsIDcpDQoNCnAxIDwtIGZ2aXpfY2x1c3RlcihrbTIsIGRhdGEgPSBzY2FsZV9kYXRhLCBlbGxpcHNlLnR5cGUgPSAiY29udmV4IikgKyB0aGVtZV9taW5pbWFsKCkgKyBnZ3RpdGxlKCJrID0gMiIpIA0KcDIgPC0gZnZpel9jbHVzdGVyKGttMywgZGF0YSA9IHNjYWxlX2RhdGEsIGVsbGlwc2UudHlwZSA9ICJjb252ZXgiKSArIHRoZW1lX21pbmltYWwoKSArIGdndGl0bGUoImsgPSAzIikNCnAzIDwtIGZ2aXpfY2x1c3RlcihrbTQsIGRhdGEgPSBzY2FsZV9kYXRhLCBlbGxpcHNlLnR5cGUgPSAiY29udmV4IikgKyB0aGVtZV9taW5pbWFsKCkgKyBnZ3RpdGxlKCJrID0gNCIpDQpwNCA8LSBmdml6X2NsdXN0ZXIoa201LCBkYXRhID0gc2NhbGVfZGF0YSwgZWxsaXBzZS50eXBlID0gImNvbnZleCIpICsgdGhlbWVfbWluaW1hbCgpICsgZ2d0aXRsZSgiayA9IDUiKQ0KcDUgPC0gZnZpel9jbHVzdGVyKGttNiwgZGF0YSA9IHNjYWxlX2RhdGEsIGVsbGlwc2UudHlwZSA9ICJjb252ZXgiKSArIHRoZW1lX21pbmltYWwoKSArIGdndGl0bGUoImsgPSA2IikNCnA2IDwtIGZ2aXpfY2x1c3RlcihrbTcsIGRhdGEgPSBzY2FsZV9kYXRhLCBlbGxpcHNlLnR5cGUgPSAiY29udmV4IikgKyB0aGVtZV9taW5pbWFsKCkgKyBnZ3RpdGxlKCJrID0gNyIpDQoNCmZpZyA8LSBzdWJwbG90KHAxLCBwMiwgcDMgLCBwNCwgcDUsIHA2LCBucm93cyA9IDIsIHNoYXJlWCA9IFRSVUUsIHNoYXJlWSA9IFRSVUUpICU+JSBsYXlvdXQoKSAjIFRPT0Q6IG1ha2UgYWxsIHBsb3RzIGxpbmtlZA0KZmlnDQpgYGANCg0KDQpgYGB7cn0NCiMgdHdvIHZhcmlhYmxlcywgY29udGludW91cyB4LCBjb250aW51b3VzIHksIHNob3cgdHJlbmQgYW5kIGRpc3RyaWJ1dGlvbg0KbmFtZSA9IGMoJ3Byb2R1Y3Rpb25fb2ZfaW5fc2NvcGVfcG9seW1lcnMnLCAndG90YWxfY29udHJpYnV0aW9uX3RvX3N1cF93YXN0ZScpDQpkZiA8LSB0aWJibGUocGxhc3RpYywgY2x1c3RlciA9IGFzLmZhY3RvcihrbWVhbnNfbW9kZWwkY2x1c3RlcikpDQpkZiA8LSBkZiAlPiUgcmVuYW1lKHggPSBwcm9kdWN0aW9uX29mX2luX3Njb3BlX3BvbHltZXJzLCB5ID0gdG90YWxfY29udHJpYnV0aW9uX3RvX3N1cF93YXN0ZSwgY2x1c3RlciA9IGNsdXN0ZXIsIHRleHQgPSBwb2x5bWVyX3Byb2R1Y2VyKSAlPiUgc2VsZWN0KHgsIHksIGNsdXN0ZXIsIHRleHQpIA0KDQojIGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9nZW9tX3Ntb290aC5odG1sDQpwb2ludF9wbG90IDwtIGRmICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSB4LCB5ID0geSwgY29sb3IgPSBmYWN0b3IoY2x1c3RlcikpKSArDQogICAgIyBnZW9tX2ppdHRlcihhbHBoYSA9IDAuNSwgc2l6ZSA9IDEpICsNCiAgICBnZW9tX3J1ZyhhbHBoYSA9IDAuNSkgKyAjIHR3byAxZCBtYXJnaW5hbCBkaXN0cmlidXRpb25zLCBkaXNwbGF5IGluZGl2aWR1YWwgY2FzZXMgc28gYXJlIGJlc3QgdXNlZCB3aXRoIHNtYWxsZXIgZGF0YXNldHMNCiAgICBnZW9tX2RlbnNpdHlfMmQoYWxwaGEgPSAwLjIsIGJpbnMgPSA0KSArIyAyRCBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0aW9uIHVzaW5nIE1BU1M6OmtkZTJkKCkgYW5kIGRpc3BsYXkgdGhlIHJlc3VsdHMgd2l0aCBjb250b3Vycw0KICAgIGdlb21fc21vb3RoKGZpbGwgPSAiZ3JleTkwIikgKyAjIGFpZHMgdGhlIGV5ZSBpbiBzZWVpbmcgcGF0dGVybnMgaW4gdGhlIHByZXNlbmNlIG9mIG92ZXJwbG90dGluZw0KICAgIGdlb21fcG9pbnQoYWVzKHRleHQgPSB0ZXh0KSwgYWxwaGEgPSAwLjc1KSArICMgcG9pbnQgZ2VvbSBpcyB1c2VkIHRvIGNyZWF0ZSBzY2F0dGVycGxvdHMNCiAgICB0aGVtZV9taW5pbWFsKCkgKw0KICAgIGdndGl0bGUocGFzdGUoInRyZW5kIG9mIiwgbmFtZVsyXSwgIm92ZXIiLCBuYW1lWzFdLCBzZXA9IiAiKSkgDQpwb2ludF9wbG90IDwtIGdncGxvdGx5KHBvaW50X3Bsb3QpICU+JSBsYXlvdXQoeGF4aXMgPSBsaXN0KHNob3d0aWNrbGFiZWxzID0gRkFMU0UpKQ0KDQp4X2RlbnNpdHlfcGxvdCA8LSBkZiAlPiUNCiAgZ2dwbG90KGFlcyh4ID0geCwgY29sb3IgPSBmYWN0b3IoY2x1c3RlcikpKSArDQogICAgc3RhdF9kZW5zaXR5KGdlb209ImxpbmUiKSArICMgZHJhd3Mga2VybmVsIGRlbnNpdHkgZXN0aW1hdGUsIHdoaWNoIGlzIGEgc21vb3RoZWQgdmVyc2lvbiBvZiB0aGUgaGlzdG9ncmFtDQogICAgIyBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEpICsNCiAgICB0aGVtZV9taW5pbWFsKCkgDQp4X2RlbnNpdHlfcGxvdCA8LSBnZ3Bsb3RseSh4X2RlbnNpdHlfcGxvdCkgJT4lIGxheW91dCh5YXhpcyA9IGxpc3Qoc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSwgc2hvd2dyaWQgPSBGQUxTRSksIHhheGlzID0gbGlzdChzaG93dGlja2xhYmVscyA9IEZBTFNFLCBzaG93Z3JpZCA9IEZBTFNFKSkNCg0KeV9kZW5zaXR5X3Bsb3QgPC0gZGYgJT4lDQogIGdncGxvdChhZXMoeCA9IHksIGNvbG9yID0gZmFjdG9yKGNsdXN0ZXIpKSkgKw0KICAgIHN0YXRfZGVuc2l0eShnZW9tPSJsaW5lIikgKyAjIGRyYXdzIGtlcm5lbCBkZW5zaXR5IGVzdGltYXRlLCB3aGljaCBpcyBhIHNtb290aGVkIHZlcnNpb24gb2YgdGhlIGhpc3RvZ3JhbQ0KICAgICMgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxKSArDQogICAgY29vcmRfZmxpcCgpICsNCiAgICB0aGVtZV9taW5pbWFsKCkgDQp5X2RlbnNpdHlfcGxvdCA8LSBnZ3Bsb3RseSh5X2RlbnNpdHlfcGxvdCkgJT4lIGxheW91dCh5YXhpcyA9IGxpc3Qoc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSwgc2hvd2dyaWQgPSBGQUxTRSksIHhheGlzID0gbGlzdChzaG93dGlja2xhYmVscyA9IEZBTFNFLCBzaG93Z3JpZCA9IEZBTFNFKSkNCg0KIyBodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvZ2VvbV9xdWFudGlsZS5odG1sDQpxdWFsdGlsZV9wbG90IDwtIGRmICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSB4LCB5ID0geSwgY29sb3IgPSBmYWN0b3IoY2x1c3RlcikpKSArDQogICAgZ2VvbV9xdWFudGlsZShhbHBoYSA9IDAuOCkgKyAjIGZpdHMgYSBxdWFudGlsZSByZWdyZXNzaW9uIHRvIHRoZSBkYXRhIGFuZCBkcmF3cyB0aGUgZml0dGVkIHF1YW50aWxlcyB3aXRoIGxpbmVzDQogICAgdGhlbWVfbWluaW1hbCgpIA0KcXVhbHRpbGVfcGxvdCA8LSBnZ3Bsb3RseShxdWFsdGlsZV9wbG90KSAlPiUgbGF5b3V0KHlheGlzID0gbGlzdChzaG93dGlja2xhYmVscyA9IEZBTFNFLCBzaG93Z3JpZCA9IEZBTFNFKSkNCg0KIyBtZXJnZSBmaWd1cmVzIGludG8gb25lIHBsb3QsIHZpYSBzdWJwbG90cywgaHR0cHM6Ly9wbG90bHktci5jb20vYXJyYW5naW5nLXZpZXdzLmh0bWwNCnN1YjEgPC0gc3VicGxvdCh4X2RlbnNpdHlfcGxvdCwgcGxvdGx5X2VtcHR5KCksIHBvaW50X3Bsb3QsIHlfZGVuc2l0eV9wbG90LCBucm93cyA9IDIsIG1hcmdpbiA9IDAsIGhlaWdodHMgPSBjKDAuMTUsIDAuODUpLCB3aWR0aHMgPSBjKDAuOSwgMC4xKSwgc2hhcmVYID0gVFJVRSwgc2hhcmVZID0gVFJVRSwgdGl0bGVYID0gRkFMU0UsIHRpdGxlWSA9IEZBTFNFKSAlPiUgbGF5b3V0KCkNCnN1YjIgPC0gc3VicGxvdChxdWFsdGlsZV9wbG90LCBwbG90bHlfZW1wdHkoKSwgbWFyZ2luID0gMCwgd2lkdGhzID0gYygwLjksIDAuMTApLCB0aXRsZVggPSBGQUxTRSwgdGl0bGVZID0gRkFMU0UpICU+JSBsYXlvdXQoKQ0KZmlnIDwtIHN1YnBsb3Qoc3ViMSwgc3ViMiwgbnJvd3MgPSAyLCBtYXJnaW4gPSAwLCBoZWlnaHRzID0gYygwLjgsIDAuMiksIHNoYXJlWCA9IFRSVUUpICU+JSBsYXlvdXQoeGF4aXMgPSBsaXN0KHRpdGxlID0gbmFtZVsxXSksIHlheGlzID0gbGlzdCh0aXRsZSA9IG5hbWVbMl0pKQ0KDQpmaWcNCmBgYA0KDQotLS0NCmhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nDQotLS0NCmh0dHBzOi8vc3RhdHNhbmRyLmNvbS9ibG9nL2NsdXN0ZXJpbmctYW5hbHlzaXMtay1tZWFucy1hbmQtaGllcmFyY2hpY2FsLWNsdXN0ZXJpbmctYnktaGFuZC1hbmQtaW4tci8jb3B0aW1hbC1udW1iZXItb2YtY2x1c3RlcnMNCnJlbWluZCB0aGF0IHRoZSBkaWZmZXJlbmNlIHdpdGggdGhlIHBhcnRpdGlvbiBieSBrLW1lYW5zIGlzIHRoYXQgZm9yIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nLCB0aGUgbnVtYmVyIG9mIGNsYXNzZXMgaXMgbm90IHNwZWNpZmllZCBpbiBhZHZhbmNlDQoNCml0IHNlZW1zIHRoYXQgbW9zdCBjbHVzdGVycyBhcmUgY29uZnVzZWQgYnkgc29tZSBzcGVjaWZpYyB2YWx1ZXMgKDgxLCA2MywgNzIsIDM2PyksIGFyZSB0aGV5IHNvbWUgc29ydCBvZiBvdXRsaWVyLCBvciB3aGF0IGlzIHNwZWNpYWwgYWJvdXQgdGhlbT8NCg0KYGBge3J9DQpuYW1lID0gYygncG9seW1lcl9wcm9kdWNlcicpDQpkZiA8LSBwbGFzdGljICU+JSBzZWxlY3QoLSB0b3RhbF93YXN0ZV9kaXZfcHJvZHVjdGlvbiwgLXJhbmssIC1ub19vZl9hc3NldHMsIC10b3RhbF9jb250cmlidXRpb25fdG9fc3VwX3dhc3RlKSAlPiUgIyByZW1vdmVkIHZhcmlhYmxlcyB3aGljaCBhcmUgZGVwZW5kZWQgb24gZWFjaCBvdGhlcg0KICByZW5hbWUoIHByb2R1Y3QgPSBwcm9kdWN0aW9uX29mX2luX3Njb3BlX3BvbHltZXJzLCANCiAgICAgICAgICBmbGV4aWJsZSA9IGZsZXhpYmxlX2Zvcm1hdF9jb250cmlidXRpb25fdG9fc3VwX3dhc3RlLCANCiAgICAgICAgICByaWdpZCA9IHJpZ2lkX2Zvcm1hdF9jb250cmlidXRpb25fdG9fc3VwX3dhc3RlKQ0KDQpzY2FsZV9kYXRhIDwtIGFzLm1hdHJpeCh0KHNjYWxlKHQoZGZbLCAhbmFtZXMoZGYpICVpbiUgbmFtZV0pKSkpICMgV2UgbmVlZCB0byBub3JtYWxpemUgdGhlIGRhdGEgYmFzZWQgb24gc2NhbGUgZnVuY3Rpb24gYmVjYXVzZSB0aGUgdmFyaWFibGVzIGFyZSBkaWZmZXJlbnQgc2NhbGVzOyBOb3JtYWxpemF0aW9uIG1lYW5zIHN1YnRyYWN0aW5nIG1lYW4gZnJvbSBlYWNoIG9ic2VydmF0aW9uIGFuZCBkaXZpZGluZyB3aXRoIHN0YW5kYXJkIGRldmlhdGlvbi4gQ2hlY2sgYWxsIHRoZSB2YXJpYWJsZXMgbWVhbiB2YWx1ZXMgYXJlIHplcm8gbm93DQpoZWFkKHNjYWxlX2RhdGEpDQoNCm5vX2sgPSAyOw0KDQojIEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nOiBzaW5nbGUgbGlua2FnZQ0KaGNsdXN0X3JlcyA8LSBoY2x1c3QoZGlzdChzY2FsZV9kYXRhKSwgbWV0aG9kID0gJ3NpbmdsZScpDQpmdml6X2RlbmQoaGNsdXN0X3JlcywgayA9IG5vX2ssIHJlY3QgPSBUUlVFKQ0KDQojIEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nOiBjb21wbGV0ZSBsaW5rYWdlDQpoY2x1c3RfcmVzIDwtIGhjbHVzdChkaXN0KHNjYWxlX2RhdGEpLCBtZXRob2QgPSAnY29tcGxldGUnKQ0KcGxvdChoY2x1c3RfcmVzKQ0KcmVjdC5oY2x1c3QoaGNsdXN0X3JlcywgayA9IG5vX2ssIGJvcmRlciA9ICdibHVlJykNCg0KIyBIaWVyYXJjaGljYWwgY2x1c3RlcmluZzogYXZlcmFnZSBsaW5rYWdlDQpoY2x1c3RfcmVzIDwtIGhjbHVzdChkaXN0KHNjYWxlX2RhdGEpLCBtZXRob2QgPSAnYXZlcmFnZScpDQpwbG90KGhjbHVzdF9yZXMpDQpyZWN0LmhjbHVzdChoY2x1c3RfcmVzLCBrID0gbm9faywgYm9yZGVyID0gJ2JsdWUnKQ0KDQojIEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nOiB3YXJkDQpoY2x1c3RfcmVzIDwtIGhjbHVzdChkaXN0KHNjYWxlX2RhdGEpLCBtZXRob2QgPSAnd2FyZC5EJykNCmZ2aXpfZGVuZChoY2x1c3RfcmVzLCBrID0gbm9faywgcmVjdCA9IFRSVUUpDQoNCiMgSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmc6IG1jcXVpdHR5DQpoY2x1c3RfcmVzIDwtIGhjbHVzdChkaXN0KHNjYWxlX2RhdGEpLCBtZXRob2QgPSAnbWNxdWl0dHknKQ0KcGxvdChoY2x1c3RfcmVzKQ0KcmVjdC5oY2x1c3QoaGNsdXN0X3JlcywgayA9IG5vX2ssIGJvcmRlciA9ICdibHVlJykNCg0KIyBIaWVyYXJjaGljYWwgY2x1c3RlcmluZzogY2VudHJvaWQNCmhjbHVzdF9yZXMgPC0gaGNsdXN0KGRpc3Qoc2NhbGVfZGF0YSksIG1ldGhvZCA9ICdjZW50cm9pZCcpDQpwbG90KGhjbHVzdF9yZXMpDQpyZWN0LmhjbHVzdChoY2x1c3RfcmVzLCBrID0gbm9faywgYm9yZGVyID0gJ2JsdWUnKQ0KYGBgDQoNCi0tLQ0KQ2x1c3RyZWUNCi0tLQ0KaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tLzEwLXRpcHMtZm9yLWNob29zaW5nLXRoZS1vcHRpbWFsLW51bWJlci1vZi1jbHVzdGVycy0yNzdlOTNkNzJkOTINCkluIHRoaXMgZmlndXJlIHRoZSBzaXplIG9mIGVhY2ggbm9kZSBjb3JyZXNwb25kcyB0byB0aGUgbnVtYmVyIG9mIHNhbXBsZXMgaW4gZWFjaCBjbHVzdGVyLCBhbmQgdGhlIGFycm93cyBhcmUgY29sb3VyZWQgYWNjb3JkaW5nIHRvIHRoZSBudW1iZXIgb2Ygc2FtcGxlcyBlYWNoIGNsdXN0ZXIgcmVjZWl2ZXMuIEEgc2VwYXJhdGUgc2V0IG9mIGFycm93cywgdGhlIHRyYW5zcGFyZW50IG9uZXMsIGNhbGxlZCB0aGUgaW5jb21pbmcgbm9kZSBwcm9wb3J0aW9uLCBhcmUgYWxzbyBjb2xvdXJlZCBhbmQgc2hvd3MgaG93IHNhbXBsZXMgZnJvbSBvbmUgZ3JvdXAgZW5kIHVwIGluIGFub3RoZXIgZ3JvdXAg4oCUIGFuIGluZGljYXRvciBvZiBjbHVzdGVyIGluc3RhYmlsaXR5Lg0KDQpgYGB7ciwgZmlnLndpZHRoID0gMTUsIGZpZy5oZWlnaHQgPSA5fQ0KbmFtZSA9IGMoJ3BvbHltZXJfcHJvZHVjZXInKQ0KZGYgPC0gcGxhc3RpYyAlPiUgc2VsZWN0KC0gdG90YWxfd2FzdGVfZGl2X3Byb2R1Y3Rpb24sIC1yYW5rLCAtbm9fb2ZfYXNzZXRzLCAtdG90YWxfY29udHJpYnV0aW9uX3RvX3N1cF93YXN0ZSkgJT4lICMgcmVtb3ZlZCB2YXJpYWJsZXMgd2hpY2ggYXJlIGRlcGVuZGVkIG9uIGVhY2ggb3RoZXINCiAgcmVuYW1lKCBwcm9kdWN0ID0gcHJvZHVjdGlvbl9vZl9pbl9zY29wZV9wb2x5bWVycywgDQogICAgICAgICAgZmxleGlibGUgPSBmbGV4aWJsZV9mb3JtYXRfY29udHJpYnV0aW9uX3RvX3N1cF93YXN0ZSwgDQogICAgICAgICAgcmlnaWQgPSByaWdpZF9mb3JtYXRfY29udHJpYnV0aW9uX3RvX3N1cF93YXN0ZSkNCg0KIyBjb25maWcNCm5vX29mX2NsdXN0ZXIgPSA5DQoNCmxpYnJhcnkoY2x1c3RyZWUpICMgcHJvZHVjZSBjbHVzdGVyaW5nIHRyZWVzLCBhIHZpc3VhbGl6YXRpb24gZm9yIGludGVycm9nYXRpbmcgY2x1c3RlcmluZ3MgYXMgcmVzb2x1dGlvbiBpbmNyZWFzZXMNCg0Kc2NhbGVfZGF0YSA8LSBhcy5tYXRyaXgodChzY2FsZSh0KGRmWywgIW5hbWVzKGRmKSAlaW4lIG5hbWVdKSkpKSAjIFdlIG5lZWQgdG8gbm9ybWFsaXplIHRoZSBkYXRhIGJhc2VkIG9uIHNjYWxlIGZ1bmN0aW9uIGJlY2F1c2UgdGhlIHZhcmlhYmxlcyBhcmUgZGlmZmVyZW50IHNjYWxlczsgTm9ybWFsaXphdGlvbiBtZWFucyBzdWJ0cmFjdGluZyBtZWFuIGZyb20gZWFjaCBvYnNlcnZhdGlvbiBhbmQgZGl2aWRpbmcgd2l0aCBzdGFuZGFyZCBkZXZpYXRpb24uIENoZWNrIGFsbCB0aGUgdmFyaWFibGVzIG1lYW4gdmFsdWVzIGFyZSB6ZXJvIG5vdw0KaGVhZChzY2FsZV9kYXRhKQ0KDQp0bXAgPC0gTlVMTA0KZm9yIChrIGluIDE6bm9fb2ZfY2x1c3Rlcil7DQogIHRtcFtrXSA8LSBrbWVhbnMoc2NhbGVfZGF0YSwgaywgbnN0YXJ0ID0gMzApDQp9DQoNCnRtcF9kZiA8LSBkYXRhLmZyYW1lKHRtcCkNCmNvbG5hbWVzKHRtcF9kZikgPC0gc2VxKDE6bm9fb2ZfY2x1c3RlcikgIyBhZGQgcHJlZml4IHRvIHRoZSBjb2x1bW4gbmFtZXMNCmNvbG5hbWVzKHRtcF9kZikgPC0gcGFzdGUwKCJrIiwgY29sbmFtZXModG1wX2RmKSkgDQoNCiMgZ2V0IGluZGl2aWR1YWwgUENBDQp0bXBfZGYucGNhIDwtIHByY29tcCh0bXBfZGYsIGNlbnRlciA9IFRSVUUsIHNjYWxlLiA9IEZBTFNFKQ0KDQppbmQuY29vcmQgPC0gdG1wX2RmLnBjYSR4DQppbmQuY29vcmQgPC0gaW5kLmNvb3JkWywxOjJdDQoNCnRtcF9kZiA8LSBiaW5kX2NvbHMoYXMuZGF0YS5mcmFtZSh0bXBfZGYpLCBhcy5kYXRhLmZyYW1lKGluZC5jb29yZCkpDQoNCmNsdXN0cmVlKHRtcF9kZiwgcHJlZml4ID0gImsiKSAjIHByb2R1Y2UgY2x1c3RlcmluZyB0cmVlcywgYSB2aXN1YWxpemF0aW9uIGZvciBpbnRlcnJvZ2F0aW5nIGNsdXN0ZXJpbmcgYXMgcmVzb2x1dGlvbiBpbmNyZWFzZXMNCg0Kb3ZlcmxheV9saXN0IDwtIGNsdXN0cmVlX292ZXJsYXkodG1wX2RmLCBwcmVmaXggPSAiayIsIHhfdmFsdWUgPSAiUEMxIiwgeV92YWx1ZSA9ICJQQzIiLCBwbG90X3NpZGVzID0gVFJVRSkNCm92ZXJsYXlfbGlzdCRvdmVybGF5DQpvdmVybGF5X2xpc3QkeF9zaWRlDQpvdmVybGF5X2xpc3QkeV9zaWRlDQpgYGANCg0KLS0tDQpjVmFsaWQgdG8gY2hvb3NlIGJlc3QgY2x1c3RlcmluZyBhbGdvDQotLS0NCmh0dHBzOi8vdG93YXJkc2RhdGFzY2llbmNlLmNvbS8xMC10aXBzLWZvci1jaG9vc2luZy10aGUtb3B0aW1hbC1udW1iZXItb2YtY2x1c3RlcnMtMjc3ZTkzZDcyZDkyDQpUaGUgY1ZhbGlkIHBhY2thZ2UgY2FuIGJlIHVzZWQgdG8gc2ltdWx0YW5lb3VzbHkgY29tcGFyZSBtdWx0aXBsZSBjbHVzdGVyaW5nIGFsZ29yaXRobXMsIHRvIGlkZW50aWZ5IHRoZSBiZXN0IGNsdXN0ZXJpbmcgYXBwcm9hY2ggYW5kIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycy4gV2Ugd2lsbCBjb21wYXJlIGstbWVhbnMsIGhpZXJhcmNoaWNhbCBhbmQgUEFNIGNsdXN0ZXJpbmcuDQoNCmBgYHtyfQ0KDQpgYGANCg0KDQoNCg0K